• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.isSUWMode;
19 import static com.android.wallpaper.util.ActivityUtils.isWallpaperOnlyMode;
20 import static com.android.wallpaper.util.ActivityUtils.startActivityForResultSafely;
21 
22 import android.annotation.SuppressLint;
23 import android.app.Activity;
24 import android.content.Intent;
25 import android.content.pm.ActivityInfo;
26 import android.content.res.Configuration;
27 import android.os.Bundle;
28 import android.text.TextUtils;
29 import android.util.Log;
30 import android.view.Window;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.core.view.WindowCompat;
35 import androidx.fragment.app.Fragment;
36 import androidx.fragment.app.FragmentActivity;
37 import androidx.fragment.app.FragmentManager;
38 import androidx.lifecycle.ViewModelProvider;
39 
40 import com.android.wallpaper.R;
41 import com.android.wallpaper.config.BaseFlags;
42 import com.android.wallpaper.model.Category;
43 import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController;
44 import com.android.wallpaper.model.PermissionRequester;
45 import com.android.wallpaper.model.WallpaperCategory;
46 import com.android.wallpaper.model.WallpaperInfo;
47 import com.android.wallpaper.model.WallpaperPreviewNavigator;
48 import com.android.wallpaper.module.DailyLoggingAlarmScheduler;
49 import com.android.wallpaper.module.Injector;
50 import com.android.wallpaper.module.InjectorProvider;
51 import com.android.wallpaper.module.LargeScreenMultiPanesChecker;
52 import com.android.wallpaper.module.MultiPanesChecker;
53 import com.android.wallpaper.module.NetworkStatusNotifier;
54 import com.android.wallpaper.module.NetworkStatusNotifier.NetworkStatus;
55 import com.android.wallpaper.module.logging.UserEventLogger;
56 import com.android.wallpaper.picker.AppbarFragment.AppbarFragmentHost;
57 import com.android.wallpaper.picker.CategorySelectorFragment.CategorySelectorFragmentHost;
58 import com.android.wallpaper.picker.MyPhotosStarter.PermissionChangedListener;
59 import com.android.wallpaper.picker.category.ui.viewmodel.CategoriesViewModel;
60 import com.android.wallpaper.util.ActivityUtils;
61 import com.android.wallpaper.util.DeepLinkUtils;
62 import com.android.wallpaper.util.DisplayUtils;
63 import com.android.wallpaper.util.LaunchUtils;
64 import com.android.wallpaper.widget.BottomActionBar;
65 import com.android.wallpaper.widget.BottomActionBar.BottomActionBarHost;
66 
67 import dagger.hilt.android.AndroidEntryPoint;
68 
69 /**
70  *  Main Activity allowing containing view sections for the user to switch between the different
71  *  Fragments providing customization options.
72  */
73 @AndroidEntryPoint(FragmentActivity.class)
74 public class CustomizationPickerActivity extends Hilt_CustomizationPickerActivity implements
75         AppbarFragmentHost, WallpapersUiContainer, BottomActionBarHost, FragmentTransactionChecker,
76         PermissionRequester, CategorySelectorFragmentHost, WallpaperPreviewNavigator {
77 
78     private static final String TAG = "CustomizationPickerActivity";
79     private static final String EXTRA_DESTINATION = "destination";
80 
81     private WallpaperPickerDelegate mDelegate;
82     private UserEventLogger mUserEventLogger;
83     private NetworkStatusNotifier mNetworkStatusNotifier;
84     private NetworkStatusNotifier.Listener mNetworkStatusListener;
85     @NetworkStatus private int mNetworkStatus;
86     private DisplayUtils mDisplayUtils;
87 
88     private BottomActionBar mBottomActionBar;
89     private boolean mIsSafeToCommitFragmentTransaction;
90 
91     private CategoriesViewModel mCategoriesViewModel;
92 
93     @Override
onCreate(@ullable Bundle savedInstanceState)94     protected void onCreate(@Nullable Bundle savedInstanceState) {
95         Injector injector = InjectorProvider.getInjector();
96         mDelegate = new WallpaperPickerDelegate(this, this, injector);
97         mUserEventLogger = injector.getUserEventLogger();
98         mNetworkStatusNotifier = injector.getNetworkStatusNotifier(this);
99         mNetworkStatus = mNetworkStatusNotifier.getNetworkStatus();
100         mDisplayUtils = injector.getDisplayUtils(this);
101         enforcePortraitForHandheldAndFoldedDisplay();
102 
103         BaseFlags flags = injector.getFlags();
104         if (flags.isMultiCropEnabled()) {
105             getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
106         }
107 
108         // Restore this Activity's state before restoring contained Fragments state.
109         super.onCreate(savedInstanceState);
110 
111         // Trampoline for the two panes
112         final MultiPanesChecker mMultiPanesChecker = new LargeScreenMultiPanesChecker();
113         if (mMultiPanesChecker.isMultiPanesEnabled(this)) {
114             Intent intent = getIntent();
115             if (!ActivityUtils.isLaunchedFromSettingsTrampoline(intent)
116                     && !ActivityUtils.isLaunchedFromSettingsRelated(intent)) {
117                 startActivityForResultSafely(this,
118                         mMultiPanesChecker.getMultiPanesIntent(intent), /* requestCode= */ 0);
119                 finish();
120                 return;
121             }
122         }
123 
124         setContentView(R.layout.activity_customization_picker);
125         mBottomActionBar = findViewById(R.id.bottom_actionbar);
126 
127         // See go/pdr-edge-to-edge-guide.
128         WindowCompat.setDecorFitsSystemWindows(getWindow(), isSUWMode(this));
129 
130         final boolean startFromLockScreen = getIntent() == null
131                 || !ActivityUtils.isLaunchedFromLauncher(getIntent());
132 
133         Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
134         if (fragment == null) {
135             // App launch specific logic: log the "app launch source" event.
136             if (getIntent() != null) {
137                 mUserEventLogger.logAppLaunched(getIntent());
138             }
139             injector.getPreferences(this).incrementAppLaunched();
140             DailyLoggingAlarmScheduler.setAlarm(getApplicationContext());
141 
142             // Switch to the target fragment.
143             switchFragment(isWallpaperOnlyMode(getIntent())
144                     ? WallpaperOnlyFragment.newInstance()
145                     : CustomizationPickerFragment.newInstance(startFromLockScreen));
146 
147 
148             if (flags.isWallpaperCategoryRefactoringEnabled()) {
149                 // initializing the dependency graph for categories
150                 mCategoriesViewModel = new ViewModelProvider(this).get(CategoriesViewModel.class);
151                 mCategoriesViewModel.initialize();
152             } else {
153                 // Cache the categories, but only if we're not restoring state (b/276767415).
154                 mDelegate.prefetchCategories();
155             }
156         }
157 
158         if (savedInstanceState == null) {
159             // We only want to start a new undo session if this activity is brand-new. A non-new
160             // activity will have a non-null savedInstanceState.
161             injector.getUndoInteractor(this, this).startSession();
162         }
163 
164         final Intent intent = getIntent();
165         final String navigationDestination = intent.getStringExtra(EXTRA_DESTINATION);
166         // Consume the destination and commit the intent back so the OS doesn't revert to the same
167         // destination when we change color or wallpaper (which causes the activity to be
168         // recreated).
169         intent.removeExtra(EXTRA_DESTINATION);
170         setIntent(intent);
171 
172         final String deepLinkCollectionId = DeepLinkUtils.getCollectionId(intent);
173 
174         if (!TextUtils.isEmpty(navigationDestination)) {
175             // Navigation deep link case
176             fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
177             if (fragment instanceof CustomizationSectionNavigationController) {
178                 final CustomizationSectionNavigationController navController =
179                         (CustomizationSectionNavigationController) fragment;
180                 navController.standaloneNavigateTo(navigationDestination);
181             }
182         } else if (!TextUtils.isEmpty(deepLinkCollectionId)) {
183             // Wallpaper Collection deep link case
184             switchFragmentWithBackStack(new CategorySelectorFragment());
185             switchFragmentWithBackStack(InjectorProvider.getInjector().getIndividualPickerFragment(
186                     this, deepLinkCollectionId));
187             intent.setData(null);
188         }
189     }
190 
191     @Override
onStart()192     protected void onStart() {
193         super.onStart();
194         if (mNetworkStatusListener == null) {
195             mNetworkStatusListener = status -> {
196                 if (status == mNetworkStatus) {
197                     return;
198                 }
199                 Log.i(TAG, "Network status changes, refresh wallpaper categories.");
200                 mNetworkStatus = status;
201                 mDelegate.initialize(/* forceCategoryRefresh= */ true);
202             };
203             // Upon registering a listener, the onNetworkChanged method is immediately called with
204             // the initial network status.
205             mNetworkStatusNotifier.registerListener(mNetworkStatusListener);
206         }
207     }
208 
209     @Override
onResume()210     protected void onResume() {
211         super.onResume();
212         mIsSafeToCommitFragmentTransaction = true;
213     }
214 
215     @Override
onPause()216     protected void onPause() {
217         super.onPause();
218         mIsSafeToCommitFragmentTransaction = false;
219     }
220 
221     @Override
onStop()222     protected void onStop() {
223         if (mNetworkStatusListener != null) {
224             mNetworkStatusNotifier.unregisterListener(mNetworkStatusListener);
225             mNetworkStatusListener = null;
226         }
227         super.onStop();
228     }
229 
230     @Override
onBackPressed()231     public void onBackPressed() {
232         Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
233         if (fragment instanceof BottomActionBarFragment
234                 && ((BottomActionBarFragment) fragment).onBackPressed()) {
235             return;
236         }
237 
238         if (getSupportFragmentManager().popBackStackImmediate()) {
239             return;
240         }
241         if (moveTaskToBack(false)) {
242             return;
243         }
244         super.onBackPressed();
245     }
246 
switchFragment(Fragment fragment)247     private void switchFragment(Fragment fragment) {
248         getSupportFragmentManager()
249                 .beginTransaction()
250                 .replace(R.id.fragment_container, fragment)
251                 .commitNow();
252     }
253 
switchFragmentWithBackStack(Fragment fragment)254     private void switchFragmentWithBackStack(Fragment fragment) {
255         getSupportFragmentManager()
256                 .beginTransaction()
257                 .replace(R.id.fragment_container, fragment)
258                 .addToBackStack(null)
259                 .commit();
260         getSupportFragmentManager().executePendingTransactions();
261     }
262 
263 
264     @Override
requestExternalStoragePermission(PermissionChangedListener listener)265     public void requestExternalStoragePermission(PermissionChangedListener listener) {
266         mDelegate.requestExternalStoragePermission(listener);
267     }
268 
269     @Override
showViewOnlyPreview(WallpaperInfo wallpaperInfo, boolean isAssetIdPresent)270     public void showViewOnlyPreview(WallpaperInfo wallpaperInfo, boolean isAssetIdPresent) {
271         mDelegate.showViewOnlyPreview(wallpaperInfo, isAssetIdPresent);
272     }
273 
274     @Override
requestCustomPhotoPicker(PermissionChangedListener listener)275     public void requestCustomPhotoPicker(PermissionChangedListener listener) {
276         mDelegate.requestCustomPhotoPicker(listener);
277     }
278 
279     @Override
show(Category category)280     public void show(Category category) {
281         if (!(category instanceof WallpaperCategory)) {
282             mDelegate.show(category.getCollectionId());
283             return;
284         }
285         switchFragmentWithBackStack(InjectorProvider.getInjector().getIndividualPickerFragment(
286                 this, category.getCollectionId()));
287     }
288 
289     @Override
fetchCategories()290     public void fetchCategories() {
291         mDelegate.initialize(mDelegate.getCategoryProvider().shouldForceReload(this));
292     }
293 
294     @Override
cleanUp()295     public void cleanUp() {
296         mDelegate.cleanUp();
297     }
298 
299     @Override
onWallpapersReady()300     public void onWallpapersReady() {
301 
302     }
303 
304     @Nullable
305     @Override
getCategorySelectorFragment()306     public CategorySelectorFragment getCategorySelectorFragment() {
307         FragmentManager fm = getSupportFragmentManager();
308         Fragment fragment = fm.findFragmentById(R.id.fragment_container);
309         if (fragment instanceof CategorySelectorFragment) {
310             return (CategorySelectorFragment) fragment;
311         } else {
312             return null;
313         }
314     }
315 
316     @Override
onDestroy()317     protected void onDestroy() {
318         super.onDestroy();
319     }
320 
321     @Override
doneFetchingCategories()322     public void doneFetchingCategories() {
323 
324     }
325 
326     @SuppressWarnings("MissingSuperCall") // TODO: Fix me
327     @Override
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)328     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
329             @NonNull int[] grantResults) {
330         mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
331     }
332 
333     @Override
onActivityResult(int requestCode, int resultCode, @Nullable Intent data)334     protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
335         super.onActivityResult(requestCode, resultCode, data);
336         if (mDelegate.handleActivityResult(requestCode, resultCode, data)) {
337             if (isSUWMode(this)) {
338                 finishActivityForSUW();
339             } else {
340                 // We don't finish in the revamped UI to let the user have a chance to reset the
341                 // change they made, should they want to. We do, however, remove all the fragments
342                 // from our back stack to reveal the root fragment, revealing the main screen of the
343                 // app.
344                 final FragmentManager fragmentManager = getSupportFragmentManager();
345                 while (fragmentManager.getBackStackEntryCount() > 0) {
346                     fragmentManager.popBackStackImmediate();
347                 }
348             }
349         }
350     }
351 
finishActivityWithResultOk()352     private void finishActivityWithResultOk() {
353         overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
354         setResult(Activity.RESULT_OK);
355         finish();
356 
357         // Go back to launcher home
358         LaunchUtils.launchHome(this);
359     }
360 
finishActivityForSUW()361     private void finishActivityForSUW() {
362         overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
363         // Return RESULT_CANCELED to make the "Change wallpaper" tile in SUW not be disabled.
364         setResult(Activity.RESULT_CANCELED);
365         finish();
366     }
367 
368     @Override
getBottomActionBar()369     public BottomActionBar getBottomActionBar() {
370         return mBottomActionBar;
371     }
372 
373     @Override
isSafeToCommitFragmentTransaction()374     public boolean isSafeToCommitFragmentTransaction() {
375         return mIsSafeToCommitFragmentTransaction;
376     }
377 
378     @Override
onUpArrowPressed()379     public void onUpArrowPressed() {
380         // TODO(b/189166781): Remove interface AppbarFragmentHost#onUpArrowPressed.
381         onBackPressed();
382     }
383 
384     @Override
isUpArrowSupported()385     public boolean isUpArrowSupported() {
386         return !isSUWMode(this);
387     }
388 
389     @Override
onConfigurationChanged(@onNull Configuration newConfig)390     public void onConfigurationChanged(@NonNull Configuration newConfig) {
391         super.onConfigurationChanged(newConfig);
392         enforcePortraitForHandheldAndFoldedDisplay();
393     }
394 
395     /**
396      * If the display is a handheld display or a folded display from a foldable, we enforce the
397      * activity to be portrait.
398      *
399      * This method should be called upon initialization of this activity, and whenever there is a
400      * configuration change.
401      */
402     @SuppressLint("SourceLockedOrientationActivity")
enforcePortraitForHandheldAndFoldedDisplay()403     private void enforcePortraitForHandheldAndFoldedDisplay() {
404         int wantedOrientation = mDisplayUtils.isLargeScreenOrUnfoldedDisplay(this)
405                 ? ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
406                 : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
407         if (getRequestedOrientation() != wantedOrientation) {
408             setRequestedOrientation(wantedOrientation);
409         }
410     }
411 }
412