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