diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 031fb8a9d92..fd67aa813b7 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -77,6 +77,7 @@ import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.RawContacts; +import android.provider.Settings; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.Spannable; @@ -109,6 +110,7 @@ import androidx.preference.PreferenceGroup; import com.android.internal.app.UnlaunchableAppActivity; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockPatternUtils; +import com.android.settings.core.FeatureFlags; import com.android.settings.dashboard.profileselector.ProfileFragmentBridge; import com.android.settings.dashboard.profileselector.ProfileSelectFragment; import com.android.settings.password.ChooseLockSettingsHelper; @@ -159,6 +161,9 @@ public final class Utils extends com.android.settingslib.Utils { /** Whether or not app hibernation is enabled on the device **/ public static final String PROPERTY_APP_HIBERNATION_ENABLED = "app_hibernation_enabled"; + /** Whether or not Settings Shared Axis transition is enabled */ + public static final String SETTINGS_SHARED_AXIS_ENABLED = "settings_shared_axis_enabled"; + /** * Finds a matching activity for a preference's intent. If a matching * activity is not found, it will remove the preference. @@ -1196,4 +1201,12 @@ public final class Utils extends com.android.settingslib.Utils { public static boolean isProviderModelEnabled(Context context) { return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); } + + public static boolean isPageTransitionEnabled(Context context) { + final boolean isSilkyHome = FeatureFlagUtils.isEnabled(context, FeatureFlags.SILKY_HOME); + final boolean isTransitionEnabled = Settings.Global.getInt(context.getContentResolver(), + SETTINGS_SHARED_AXIS_ENABLED, 0) == 1; + + return isSilkyHome && isTransitionEnabled; + } } diff --git a/src/com/android/settings/core/SettingsBaseActivity.java b/src/com/android/settings/core/SettingsBaseActivity.java index 96ee51d7f06..903805e6ddb 100644 --- a/src/com/android/settings/core/SettingsBaseActivity.java +++ b/src/com/android/settings/core/SettingsBaseActivity.java @@ -18,6 +18,7 @@ package com.android.settings.core; import android.annotation.LayoutRes; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -27,6 +28,7 @@ import android.content.pm.PackageManager; import android.content.res.TypedArray; import android.os.AsyncTask; import android.os.Bundle; +import android.os.UserHandle; import android.text.TextUtils; import android.util.ArraySet; import android.util.FeatureFlagUtils; @@ -43,8 +45,10 @@ import androidx.fragment.app.FragmentActivity; import com.android.settings.R; import com.android.settings.SubSettings; +import com.android.settings.Utils; import com.android.settings.dashboard.CategoryManager; import com.android.settingslib.drawer.Tile; +import com.android.settingslib.transition.SettingsTransitionHelper; import com.google.android.material.appbar.CollapsingToolbarLayout; import com.google.android.material.resources.TextAppearanceConfig; @@ -61,6 +65,7 @@ public class SettingsBaseActivity extends FragmentActivity { protected static final boolean DEBUG_TIMING = false; private static final String TAG = "SettingsBaseActivity"; private static final String DATA_SCHEME_PKG = "package"; + private static final int DEFAULT_REQUEST = -1; // Serves as a temporary list of tiles to ignore until we heard back from the PM that they // are disabled. @@ -74,6 +79,13 @@ public class SettingsBaseActivity extends FragmentActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { + if (Utils.isPageTransitionEnabled(this)) { + // Enable Activity transitions + getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); + SettingsTransitionHelper.applyForwardTransition(this); + SettingsTransitionHelper.applyBackwardTransition(this); + } + super.onCreate(savedInstanceState); if (isLockTaskModePinned() && !isSettingsRunOnTop()) { Log.w(TAG, "Devices lock task mode pinned."); @@ -124,6 +136,57 @@ public class SettingsBaseActivity extends FragmentActivity { return true; } + @Override + public void startActivity(Intent intent) { + if (!Utils.isPageTransitionEnabled(this)) { + super.startActivity(intent); + return; + } + super.startActivity(intent, getActivityOptionsBundle()); + } + + @Override + public void startActivity(Intent intent, @androidx.annotation.Nullable Bundle options) { + if (!Utils.isPageTransitionEnabled(this) || options != null) { + super.startActivity(intent, options); + return; + } + super.startActivity(intent, getActivityOptionsBundle()); + } + + @Override + public void startActivityForResult(Intent intent, int requestCode) { + // startActivity() will eventually calls startActivityForResult() with requestCode -1. + // Adding this condition to avoid multiple calls. + if (!Utils.isPageTransitionEnabled(this) || requestCode == DEFAULT_REQUEST) { + super.startActivityForResult(intent, requestCode); + return; + } + super.startActivityForResult(intent, requestCode, getActivityOptionsBundle()); + } + + @Override + public void startActivityForResult(Intent intent, int requestCode, + @androidx.annotation.Nullable Bundle options) { + if (!Utils.isPageTransitionEnabled(this) || requestCode == DEFAULT_REQUEST + || options != null) { + super.startActivityForResult(intent, requestCode, options); + return; + } + super.startActivityForResult(intent, requestCode, getActivityOptionsBundle()); + } + + @Override + public void startActivityForResultAsUser(Intent intent, int requestCode, + UserHandle userHandle) { + if (!Utils.isPageTransitionEnabled(this) || requestCode == DEFAULT_REQUEST) { + super.startActivityForResultAsUser(intent, requestCode, userHandle); + return; + } + super.startActivityForResultAsUser(intent, requestCode, getActivityOptionsBundle(), + userHandle); + } + @Override protected void onResume() { super.onResume(); @@ -269,10 +332,16 @@ public class SettingsBaseActivity extends FragmentActivity { } } + private Bundle getActivityOptionsBundle() { + final Toolbar toolbar = findViewById(R.id.action_bar); + return ActivityOptions.makeSceneTransitionAnimation(this, toolbar, + "shared_element_view").toBundle(); + } + public interface CategoryListener { /** * @param categories the changed categories that have to be refreshed, or null to force - * refreshing all. + * refreshing all. */ void onCategoriesChanged(@Nullable Set categories); } diff --git a/src/com/android/settings/core/SubSettingLauncher.java b/src/com/android/settings/core/SubSettingLauncher.java index 5d9a5282021..545fcdad4ab 100644 --- a/src/com/android/settings/core/SubSettingLauncher.java +++ b/src/com/android/settings/core/SubSettingLauncher.java @@ -17,18 +17,22 @@ package com.android.settings.core; import android.annotation.StringRes; +import android.app.Activity; +import android.app.ActivityOptions; import android.content.Context; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.os.UserHandle; import android.text.TextUtils; +import android.widget.Toolbar; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; +import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.SubSettings; +import com.android.settings.Utils; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; public class SubSettingLauncher { @@ -183,7 +187,16 @@ public class SubSettingLauncher { resultListener.getActivity().startActivityForResultAsUser(intent, requestCode, userHandle); } - private void launchForResult(Fragment listener, Intent intent, int requestCode) { + @VisibleForTesting + void launchForResult(Fragment listener, Intent intent, int requestCode) { + if (Utils.isPageTransitionEnabled(mContext)) { + final Activity activity = listener.getActivity(); + final Toolbar toolbar = activity.findViewById(R.id.action_bar); + final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity, toolbar, + "shared_element_view").toBundle(); + listener.startActivityForResult(intent, requestCode, bundle); + return; + } listener.startActivityForResult(intent, requestCode); } @@ -192,6 +205,7 @@ public class SubSettingLauncher { intent.replaceExtras(mLaunchRequest.extras); } } + /** * Simple container that has information about how to launch a subsetting. */ diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 881e39ce2cb..cd980f32a6f 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -18,11 +18,14 @@ package com.android.settings.homepage; import android.animation.LayoutTransition; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.settings.SettingsEnums; +import android.content.Intent; import android.os.Bundle; import android.util.FeatureFlagUtils; import android.util.Log; import android.view.View; +import android.view.Window; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.Toolbar; @@ -33,11 +36,13 @@ import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settings.accounts.AvatarViewMixin; import com.android.settings.core.FeatureFlags; import com.android.settings.core.HideNonSystemOverlayMixin; import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.overlay.FeatureFactory; +import com.android.settingslib.transition.SettingsTransitionHelper; public class SettingsHomepageActivity extends FragmentActivity { @@ -64,6 +69,12 @@ public class SettingsHomepageActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { + if (Utils.isPageTransitionEnabled(this)) { + // Enable Activity transitions + getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); + SettingsTransitionHelper.applyForwardTransition(this); + SettingsTransitionHelper.applyBackwardTransition(this); + } super.onCreate(savedInstanceState); setContentView(R.layout.settings_homepage_container); @@ -101,6 +112,16 @@ public class SettingsHomepageActivity extends FragmentActivity { .getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); } + @Override + public void startActivity(Intent intent) { + if (Utils.isPageTransitionEnabled(this)) { + final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(this).toBundle(); + super.startActivity(intent, bundle); + return; + } + super.startActivity(intent); + } + private void showSuggestionFragment() { final Class fragment = FeatureFactory.getFactory(this) .getSuggestionFeatureProvider(this).getContextualSuggestionFragment(); diff --git a/tests/robotests/src/com/android/settings/core/SubSettingLauncherTest.java b/tests/robotests/src/com/android/settings/core/SubSettingLauncherTest.java index 8f9c33142d7..7a83cd01bb0 100644 --- a/tests/robotests/src/com/android/settings/core/SubSettingLauncherTest.java +++ b/tests/robotests/src/com/android/settings/core/SubSettingLauncherTest.java @@ -28,12 +28,14 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; +import android.os.Bundle; import android.os.UserHandle; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import com.android.settings.SettingsActivity; +import com.android.settings.testutils.shadow.ShadowUtils; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import org.junit.Before; @@ -42,10 +44,12 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowUtils.class) public class SubSettingLauncherTest { @Mock @@ -109,10 +113,13 @@ public class SubSettingLauncherTest { @Test public void launch_hasRequestListener_shouldStartActivityForResult() { + ShadowUtils.setIsPageTransitionEnabled(true); final int requestCode = 123123; when(mFragment.getActivity()).thenReturn(mActivity); final SubSettingLauncher launcher = spy(new SubSettingLauncher(mContext)); + doNothing().when(launcher).launchForResult(any(Fragment.class), any(Intent.class), + anyInt()); launcher.setTitleText("123") .setDestination(SubSettingLauncherTest.class.getName()) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) @@ -120,7 +127,8 @@ public class SubSettingLauncherTest { .setResultListener(mFragment, requestCode) .launch(); - verify(mFragment).startActivityForResult(any(Intent.class), eq(requestCode)); + verify(launcher) + .launchForResult(eq(mFragment), any(Intent.class), eq(requestCode)); } @Test diff --git a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java index c7a2650be54..620f6d74b17 100644 --- a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java +++ b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java @@ -39,6 +39,7 @@ import com.android.settings.core.HideNonSystemOverlayMixin; import com.android.settings.dashboard.suggestions.SuggestionFeatureProviderImpl; import com.android.settings.homepage.contextualcards.slices.BatteryFixSliceTest; import com.android.settings.testutils.shadow.ShadowUserManager; +import com.android.settings.testutils.shadow.ShadowUtils; import org.junit.Before; import org.junit.Test; @@ -58,12 +59,13 @@ import org.robolectric.util.ReflectionHelpers; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowUserManager.class, - SettingsHomepageActivityTest.ShadowSuggestionFeatureProviderImpl.class}) + SettingsHomepageActivityTest.ShadowSuggestionFeatureProviderImpl.class, ShadowUtils.class}) public class SettingsHomepageActivityTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); + ShadowUtils.setIsPageTransitionEnabled(false); } @Test diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java index 5a32f349d06..750640b225c 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java @@ -47,6 +47,7 @@ public class ShadowUtils { private static boolean sIsVoiceCapable; private static ArraySet sResultLinks = new ArraySet<>(); private static boolean sIsBatteryPresent; + private static boolean sIsPageTransitionEnabled; @Implementation protected static int enforceSameOwner(Context context, int userId) { @@ -69,6 +70,7 @@ public class ShadowUtils { sIsVoiceCapable = false; sResultLinks = new ArraySet<>(); sIsBatteryPresent = true; + sIsPageTransitionEnabled = true; } public static void setIsDemoUser(boolean isDemoUser) { @@ -166,4 +168,13 @@ public class ShadowUtils { public static void setIsBatteryPresent(boolean isBatteryPresent) { sIsBatteryPresent = isBatteryPresent; } + + @Implementation + protected static boolean isPageTransitionEnabled(Context context) { + return sIsPageTransitionEnabled; + } + + public static void setIsPageTransitionEnabled(boolean isPageTransitionEnabled) { + sIsPageTransitionEnabled = isPageTransitionEnabled; + } }