/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wallpaper.picker; import static com.android.wallpaper.util.ActivityUtils.isSUWMode; import static com.android.wallpaper.util.ActivityUtils.isWallpaperOnlyMode; import static com.android.wallpaper.util.ActivityUtils.startActivityForResultSafely; import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.Window; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.view.WindowCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import com.android.wallpaper.R; import com.android.wallpaper.config.BaseFlags; import com.android.wallpaper.model.Category; import com.android.wallpaper.model.CustomizationSectionController.CustomizationSectionNavigationController; import com.android.wallpaper.model.PermissionRequester; import com.android.wallpaper.model.WallpaperCategory; import com.android.wallpaper.model.WallpaperInfo; import com.android.wallpaper.model.WallpaperPreviewNavigator; import com.android.wallpaper.module.DailyLoggingAlarmScheduler; import com.android.wallpaper.module.Injector; import com.android.wallpaper.module.InjectorProvider; import com.android.wallpaper.module.LargeScreenMultiPanesChecker; import com.android.wallpaper.module.MultiPanesChecker; import com.android.wallpaper.module.NetworkStatusNotifier; import com.android.wallpaper.module.NetworkStatusNotifier.NetworkStatus; import com.android.wallpaper.module.logging.UserEventLogger; import com.android.wallpaper.picker.AppbarFragment.AppbarFragmentHost; import com.android.wallpaper.picker.CategorySelectorFragment.CategorySelectorFragmentHost; import com.android.wallpaper.picker.MyPhotosStarter.PermissionChangedListener; import com.android.wallpaper.picker.individual.IndividualPickerFragment.IndividualPickerFragmentHost; import com.android.wallpaper.util.ActivityUtils; import com.android.wallpaper.util.DeepLinkUtils; import com.android.wallpaper.util.DisplayUtils; import com.android.wallpaper.util.LaunchUtils; import com.android.wallpaper.widget.BottomActionBar; import com.android.wallpaper.widget.BottomActionBar.BottomActionBarHost; import dagger.hilt.android.AndroidEntryPoint; /** * Main Activity allowing containing view sections for the user to switch between the different * Fragments providing customization options. */ @AndroidEntryPoint(FragmentActivity.class) public class CustomizationPickerActivity extends Hilt_CustomizationPickerActivity implements AppbarFragmentHost, WallpapersUiContainer, BottomActionBarHost, FragmentTransactionChecker, PermissionRequester, CategorySelectorFragmentHost, IndividualPickerFragmentHost, WallpaperPreviewNavigator { private static final String TAG = "CustomizationPickerActivity"; private static final String EXTRA_DESTINATION = "destination"; private WallpaperPickerDelegate mDelegate; private UserEventLogger mUserEventLogger; private NetworkStatusNotifier mNetworkStatusNotifier; private NetworkStatusNotifier.Listener mNetworkStatusListener; @NetworkStatus private int mNetworkStatus; private DisplayUtils mDisplayUtils; private BottomActionBar mBottomActionBar; private boolean mIsSafeToCommitFragmentTransaction; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { Injector injector = InjectorProvider.getInjector(); mDelegate = new WallpaperPickerDelegate(this, this, injector); mUserEventLogger = injector.getUserEventLogger(); mNetworkStatusNotifier = injector.getNetworkStatusNotifier(this); mNetworkStatus = mNetworkStatusNotifier.getNetworkStatus(); mDisplayUtils = injector.getDisplayUtils(this); enforcePortraitForHandheldAndFoldedDisplay(); BaseFlags flags = injector.getFlags(); if (flags.isMultiCropEnabled()) { getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); } // Restore this Activity's state before restoring contained Fragments state. super.onCreate(savedInstanceState); // Trampoline for the two panes final MultiPanesChecker mMultiPanesChecker = new LargeScreenMultiPanesChecker(); if (mMultiPanesChecker.isMultiPanesEnabled(this)) { Intent intent = getIntent(); if (!ActivityUtils.isLaunchedFromSettingsTrampoline(intent) && !ActivityUtils.isLaunchedFromSettingsRelated(intent)) { startActivityForResultSafely(this, mMultiPanesChecker.getMultiPanesIntent(intent), /* requestCode= */ 0); finish(); } } setContentView(R.layout.activity_customization_picker); mBottomActionBar = findViewById(R.id.bottom_actionbar); // See go/pdr-edge-to-edge-guide. WindowCompat.setDecorFitsSystemWindows(getWindow(), isSUWMode(this)); final boolean startFromLockScreen = getIntent() == null || !ActivityUtils.isLaunchedFromLauncher(getIntent()); Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container); if (fragment == null) { // App launch specific logic: log the "app launch source" event. if (getIntent() != null) { mUserEventLogger.logAppLaunched(getIntent()); } injector.getPreferences(this).incrementAppLaunched(); DailyLoggingAlarmScheduler.setAlarm(getApplicationContext()); // Switch to the target fragment. switchFragment(isWallpaperOnlyMode(getIntent()) ? WallpaperOnlyFragment.newInstance() : CustomizationPickerFragment.newInstance(startFromLockScreen)); // Cache the categories, but only if we're not restoring state (b/276767415). mDelegate.prefetchCategories(); } if (savedInstanceState == null) { // We only want to start a new undo session if this activity is brand-new. A non-new // activity will have a non-null savedInstanceState. injector.getUndoInteractor(this, this).startSession(); } final Intent intent = getIntent(); final String navigationDestination = intent.getStringExtra(EXTRA_DESTINATION); // Consume the destination and commit the intent back so the OS doesn't revert to the same // destination when we change color or wallpaper (which causes the activity to be // recreated). intent.removeExtra(EXTRA_DESTINATION); setIntent(intent); final String deepLinkCollectionId = DeepLinkUtils.getCollectionId(intent); if (!TextUtils.isEmpty(navigationDestination)) { // Navigation deep link case fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container); if (fragment instanceof CustomizationSectionNavigationController) { final CustomizationSectionNavigationController navController = (CustomizationSectionNavigationController) fragment; navController.standaloneNavigateTo(navigationDestination); } } else if (!TextUtils.isEmpty(deepLinkCollectionId)) { // Wallpaper Collection deep link case switchFragmentWithBackStack(new CategorySelectorFragment()); switchFragmentWithBackStack(InjectorProvider.getInjector().getIndividualPickerFragment( this, deepLinkCollectionId)); intent.setData(null); } } @Override protected void onStart() { super.onStart(); if (mNetworkStatusListener == null) { mNetworkStatusListener = status -> { if (status == mNetworkStatus) { return; } Log.i(TAG, "Network status changes, refresh wallpaper categories."); mNetworkStatus = status; mDelegate.initialize(/* forceCategoryRefresh= */ true); }; // Upon registering a listener, the onNetworkChanged method is immediately called with // the initial network status. mNetworkStatusNotifier.registerListener(mNetworkStatusListener); } } @Override protected void onResume() { super.onResume(); mIsSafeToCommitFragmentTransaction = true; } @Override protected void onPause() { super.onPause(); mIsSafeToCommitFragmentTransaction = false; } @Override protected void onStop() { if (mNetworkStatusListener != null) { mNetworkStatusNotifier.unregisterListener(mNetworkStatusListener); mNetworkStatusListener = null; } super.onStop(); } @Override public void onBackPressed() { Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container); if (fragment instanceof BottomActionBarFragment && ((BottomActionBarFragment) fragment).onBackPressed()) { return; } if (getSupportFragmentManager().popBackStackImmediate()) { return; } if (moveTaskToBack(false)) { return; } super.onBackPressed(); } private void switchFragment(Fragment fragment) { getSupportFragmentManager() .beginTransaction() .replace(R.id.fragment_container, fragment) .commitNow(); } private void switchFragmentWithBackStack(Fragment fragment) { getSupportFragmentManager() .beginTransaction() .replace(R.id.fragment_container, fragment) .addToBackStack(null) .commit(); getSupportFragmentManager().executePendingTransactions(); } @Override public void requestExternalStoragePermission(PermissionChangedListener listener) { mDelegate.requestExternalStoragePermission(listener); } @Override public void showViewOnlyPreview(WallpaperInfo wallpaperInfo, boolean isAssetIdPresent) { mDelegate.showViewOnlyPreview(wallpaperInfo, isAssetIdPresent); } @Override public void requestCustomPhotoPicker(PermissionChangedListener listener) { mDelegate.requestCustomPhotoPicker(listener); } @Override public void show(Category category) { if (!(category instanceof WallpaperCategory)) { mDelegate.show(category.getCollectionId()); return; } switchFragmentWithBackStack(InjectorProvider.getInjector().getIndividualPickerFragment( this, category.getCollectionId())); } @Override public boolean isHostToolbarShown() { return false; } @Override public void setToolbarTitle(CharSequence title) { } @Override public void setToolbarMenu(int menuResId) { } @Override public void removeToolbarMenu() { } @Override public void moveToPreviousFragment() { getSupportFragmentManager().popBackStack(); } @Override public void fetchCategories() { mDelegate.initialize(mDelegate.getCategoryProvider().shouldForceReload(this)); } @Override public void cleanUp() { mDelegate.cleanUp(); } @Override public void onWallpapersReady() { } @Nullable @Override public CategorySelectorFragment getCategorySelectorFragment() { FragmentManager fm = getSupportFragmentManager(); Fragment fragment = fm.findFragmentById(R.id.fragment_container); if (fragment instanceof CategorySelectorFragment) { return (CategorySelectorFragment) fragment; } else { return null; } } @Override protected void onDestroy() { super.onDestroy(); } @Override public void doneFetchingCategories() { } @SuppressWarnings("MissingSuperCall") // TODO: Fix me @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (mDelegate.handleActivityResult(requestCode, resultCode, data)) { if (isSUWMode(this)) { finishActivityForSUW(); } else { // We don't finish in the revamped UI to let the user have a chance to reset the // change they made, should they want to. We do, however, remove all the fragments // from our back stack to reveal the root fragment, revealing the main screen of the // app. final FragmentManager fragmentManager = getSupportFragmentManager(); while (fragmentManager.getBackStackEntryCount() > 0) { fragmentManager.popBackStackImmediate(); } } } } private void finishActivityWithResultOk() { overridePendingTransition(R.anim.fade_in, R.anim.fade_out); setResult(Activity.RESULT_OK); finish(); // Go back to launcher home LaunchUtils.launchHome(this); } private void finishActivityForSUW() { overridePendingTransition(R.anim.fade_in, R.anim.fade_out); // Return RESULT_CANCELED to make the "Change wallpaper" tile in SUW not be disabled. setResult(Activity.RESULT_CANCELED); finish(); } @Override public BottomActionBar getBottomActionBar() { return mBottomActionBar; } @Override public boolean isSafeToCommitFragmentTransaction() { return mIsSafeToCommitFragmentTransaction; } @Override public void onUpArrowPressed() { // TODO(b/189166781): Remove interface AppbarFragmentHost#onUpArrowPressed. onBackPressed(); } @Override public boolean isUpArrowSupported() { return !isSUWMode(this); } @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); enforcePortraitForHandheldAndFoldedDisplay(); } /** * If the display is a handheld display or a folded display from a foldable, we enforce the * activity to be portrait. * * This method should be called upon initialization of this activity, and whenever there is a * configuration change. */ @SuppressLint("SourceLockedOrientationActivity") private void enforcePortraitForHandheldAndFoldedDisplay() { int wantedOrientation = mDisplayUtils.isLargeScreenOrUnfoldedDisplay(this) ? ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED : ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; if (getRequestedOrientation() != wantedOrientation) { setRequestedOrientation(wantedOrientation); } } }