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