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