1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.wallpaper.picker; 17 18 import static com.android.wallpaper.util.ActivityUtils.startActivityForResultSafely; 19 20 import android.Manifest.permission; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.net.Uri; 24 import android.os.Binder; 25 import android.os.Bundle; 26 import android.util.Log; 27 import android.view.WindowManager; 28 29 import androidx.annotation.NonNull; 30 import androidx.fragment.app.Fragment; 31 import androidx.fragment.app.FragmentManager; 32 33 import com.android.wallpaper.R; 34 import com.android.wallpaper.model.ImageWallpaperInfo; 35 import com.android.wallpaper.model.WallpaperInfo; 36 import com.android.wallpaper.module.InjectorProvider; 37 import com.android.wallpaper.module.LargeScreenMultiPanesChecker; 38 import com.android.wallpaper.module.MultiPanesChecker; 39 import com.android.wallpaper.module.UserEventLogger; 40 import com.android.wallpaper.picker.AppbarFragment.AppbarFragmentHost; 41 import com.android.wallpaper.util.ActivityUtils; 42 43 /** 44 * Activity that displays a preview of a specific wallpaper and provides the ability to set the 45 * wallpaper as the user's current wallpaper. It's "standalone" meaning it doesn't reside in the 46 * app navigation hierarchy and can be launched directly via an explicit intent. 47 */ 48 public class StandalonePreviewActivity extends BasePreviewActivity implements AppbarFragmentHost { 49 private static final String TAG = "StandalonePreview"; 50 private static final String KEY_UP_ARROW = "up_arrow"; 51 private static final int READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE = 1; 52 53 private UserEventLogger mUserEventLogger; 54 55 @Override onCreate(Bundle savedInstanceState)56 protected void onCreate(Bundle savedInstanceState) { 57 super.onCreate(savedInstanceState); 58 setContentView(R.layout.activity_preview); 59 60 // Trampoline for the multi-pane. 61 launchMultiPanesIfNeeded(); 62 63 enableFullScreen(); 64 65 mUserEventLogger = InjectorProvider.getInjector().getUserEventLogger(getApplicationContext()); 66 mUserEventLogger.logStandalonePreviewLaunched(); 67 68 Intent cropAndSetWallpaperIntent = getIntent(); 69 Uri imageUri = cropAndSetWallpaperIntent.getData(); 70 71 if (imageUri == null) { 72 Log.e(TAG, "No URI passed in intent; exiting StandalonePreviewActivity"); 73 finish(); 74 return; 75 } 76 77 // Check if READ_EXTERNAL_STORAGE permission is needed because the app invoking this activity 78 // passed a file:// URI or a content:// URI without a flag to grant read permission. 79 boolean isReadPermissionGrantedForImageUri = isReadPermissionGrantedForImageUri(imageUri); 80 mUserEventLogger.logStandalonePreviewImageUriHasReadPermission( 81 isReadPermissionGrantedForImageUri); 82 83 // Request storage permission if necessary (i.e., on Android M and later if storage permission 84 // has not already been granted) and delay loading the PreviewFragment until the permission is 85 // granted. 86 if (!isReadPermissionGrantedForImageUri && !isReadExternalStoragePermissionGrantedForApp()) { 87 requestPermissions( 88 new String[]{permission.READ_EXTERNAL_STORAGE}, 89 READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE); 90 } 91 } 92 93 @Override onAttachedToWindow()94 public void onAttachedToWindow() { 95 super.onAttachedToWindow(); 96 97 FragmentManager fragmentManager = getSupportFragmentManager(); 98 Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_container); 99 100 if (fragment == null) { 101 loadPreviewFragment(); 102 } 103 } 104 105 @Override onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)106 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 107 @NonNull int[] grantResults) { 108 // Load the preview fragment if the storage permission was granted. 109 if (requestCode == READ_EXTERNAL_STORAGE_PERMISSION_REQUEST_CODE) { 110 boolean isGranted = permissions.length > 0 111 && permissions[0].equals(permission.READ_EXTERNAL_STORAGE) 112 && grantResults.length > 0 113 && grantResults[0] == PackageManager.PERMISSION_GRANTED; 114 115 mUserEventLogger.logStandalonePreviewStorageDialogApproved(isGranted); 116 117 // Close the activity because we can't open the image without storage permission. 118 if (!isGranted) { 119 finish(); 120 } 121 122 loadPreviewFragment(); 123 } 124 } 125 126 // TODO(b/182972395): It should go back to WallpaperPicker. 127 @Override onUpArrowPressed()128 public void onUpArrowPressed() { 129 // Enable back functions for multi-pane. 130 onBackPressed(); 131 } 132 133 // TODO(b/182972395): It should go back to WallpaperPicker. 134 @Override isUpArrowSupported()135 public boolean isUpArrowSupported() { 136 // Show up arrow for multi-pane. 137 return getIntent().getBooleanExtra(KEY_UP_ARROW, false); 138 } 139 140 @Override enableFullScreen()141 protected void enableFullScreen() { 142 super.enableFullScreen(); 143 getWindow().setFlags( 144 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, 145 WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); 146 } 147 148 /** 149 * Launches multi-pane when it is enabled, for non-Settings' trampoline launch case will 150 * retrieve EXTRA_STREAM's image URI and assign back its intent by calling setData(). 151 */ launchMultiPanesIfNeeded()152 private void launchMultiPanesIfNeeded() { 153 MultiPanesChecker checker = new LargeScreenMultiPanesChecker(); 154 if (checker.isMultiPanesEnabled(/* context= */ this)) { 155 Intent intent = getIntent(); 156 if (!ActivityUtils.isLaunchedFromSettingsTrampoline(intent) 157 && !ActivityUtils.isLaunchedFromSettingsRelated(intent)) { 158 Uri uri = intent.getData(); 159 if (uri != null) { 160 // Grant URI permission for next launching activity. 161 grantUriPermission(getPackageName(), uri, 162 Intent.FLAG_GRANT_READ_URI_PERMISSION); 163 } 164 165 Intent previewLaunch = checker.getMultiPanesIntent(intent); 166 previewLaunch.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 167 // Put image URI and back arrow condition to separate extras. 168 .putExtra(Intent.EXTRA_STREAM, intent.getData()) 169 .putExtra(KEY_UP_ARROW, true); 170 171 startActivityForResultSafely(/* activity= */ this, previewLaunch, /* requestCode= */ 172 0); 173 finish(); 174 } else { 175 Uri uri = intent.hasExtra(Intent.EXTRA_STREAM) ? intent.getParcelableExtra( 176 Intent.EXTRA_STREAM) : null; 177 if (uri != null) { 178 intent.setData(uri); 179 } 180 } 181 } 182 } 183 184 /** 185 * Creates a new instance of {@link PreviewFragment} and loads the fragment into this activity's 186 * fragment container so that it's shown to the user. 187 */ loadPreviewFragment()188 private void loadPreviewFragment() { 189 Intent intent = getIntent(); 190 191 boolean testingModeEnabled = intent.getBooleanExtra(EXTRA_TESTING_MODE_ENABLED, false); 192 WallpaperInfo wallpaper = new ImageWallpaperInfo(intent.getData()); 193 Fragment fragment = InjectorProvider.getInjector().getPreviewFragment( 194 /* context */ this, 195 wallpaper, 196 PreviewFragment.MODE_CROP_AND_SET_WALLPAPER, 197 /* viewAsHome= */ true, 198 /* viewFullScreen= */ false, 199 testingModeEnabled); 200 getSupportFragmentManager().beginTransaction() 201 .add(R.id.fragment_container, fragment) 202 .commit(); 203 } 204 205 /** 206 * Returns whether the user has granted READ_EXTERNAL_STORAGE permission to the app. 207 */ isReadExternalStoragePermissionGrantedForApp()208 private boolean isReadExternalStoragePermissionGrantedForApp() { 209 return getPackageManager().checkPermission(permission.READ_EXTERNAL_STORAGE, 210 getPackageName()) == PackageManager.PERMISSION_GRANTED; 211 } 212 213 /** 214 * Returns whether the provided image Uri is readable without requiring the app to have the user 215 * grant READ_EXTERNAL_STORAGE permission. 216 */ isReadPermissionGrantedForImageUri(Uri imageUri)217 private boolean isReadPermissionGrantedForImageUri(Uri imageUri) { 218 return checkUriPermission( 219 imageUri, 220 Binder.getCallingPid(), 221 Binder.getCallingUid(), 222 Intent.FLAG_GRANT_READ_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED; 223 } 224 } 225