diff --git a/res/drawable/screen_resolution_1080p.xml b/res/drawable/screen_resolution_full.xml similarity index 100% rename from res/drawable/screen_resolution_1080p.xml rename to res/drawable/screen_resolution_full.xml diff --git a/res/drawable/screen_resolution_1440p.xml b/res/drawable/screen_resolution_high.xml similarity index 100% rename from res/drawable/screen_resolution_1440p.xml rename to res/drawable/screen_resolution_high.xml diff --git a/res/layout/modifier_key_reset_dialog.xml b/res/layout/modifier_key_reset_dialog.xml index fd38b11d5a8..11712bf9960 100644 --- a/res/layout/modifier_key_reset_dialog.xml +++ b/res/layout/modifier_key_reset_dialog.xml @@ -80,7 +80,7 @@ android:drawablePadding="9dp" style="@style/ModifierKeyButtonCancel" android:textColor="?android:attr/textColorPrimary" - android:text="@string/modifier_keys_restore"/> + android:text="@string/modifier_keys_reset"/> \ No newline at end of file diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml index 959bc172793..aae7403c835 100644 --- a/res/values-night/colors.xml +++ b/res/values-night/colors.xml @@ -67,5 +67,9 @@ #7DA7F1 #607DA7F1 #FFEE675C + + + + #FFFFFF diff --git a/res/values/strings.xml b/res/values/strings.xml index 44f65034c70..f593db9e544 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4008,7 +4008,7 @@ Cancel - Restore + Reset Choose modifier key @@ -4595,6 +4595,8 @@ One-handed mode added to Quick Settings. Swipe down to turn it on or off anytime. You can also add one-handed mode to Quick Settings from the top of your screen + + Font size added to Quick Settings. Swipe down to change the font size anytime. Dismiss @@ -11970,6 +11972,8 @@ On / Camera and screen flash Flash the camera light or the screen when you receive notifications or when alarms sound + + Flash the screen when you receive notifications or when alarms sound Use flash notifications with caution if you\u0027re light sensitive diff --git a/res/xml/flash_notifications_settings.xml b/res/xml/flash_notifications_settings.xml index 0017fa605f5..243fffc02a8 100644 --- a/res/xml/flash_notifications_settings.xml +++ b/res/xml/flash_notifications_settings.xml @@ -21,12 +21,12 @@ + settings:controller="com.android.settings.accessibility.FlashNotificationsIntroPreferenceController" /> + settings:lottie_rawRes="@drawable/flash_notifications_illustration" /> mSizeData; + private static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow"; private boolean mSeekByTouch; private Optional mInteractionListener = Optional.empty(); private LabeledSeekBarPreference mSeekBarPreference; + private int mLastProgress; + private boolean mNeedsQSTooltipReshow = false; + private AccessibilityQuickSettingsTooltipWindow mTooltipWindow; + private final Handler mHandler; + private final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() { @@ -54,6 +69,7 @@ class PreviewSizeSeekBarController extends BasePreferenceController implements if (!mSeekByTouch) { interactionListener.onProgressChanged(); + onProgressFinalized(); } } @@ -67,6 +83,7 @@ class PreviewSizeSeekBarController extends BasePreferenceController implements mSeekByTouch = false; mInteractionListener.ifPresent(ProgressInteractionListener::onEndTrackingTouch); + onProgressFinalized(); } }; @@ -74,6 +91,30 @@ class PreviewSizeSeekBarController extends BasePreferenceController implements @NonNull PreviewSizeData sizeData) { super(context, preferenceKey); mSizeData = sizeData; + mHandler = new Handler(context.getMainLooper()); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + // Restore the tooltip. + if (savedInstanceState != null + && savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) { + mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW); + } + } + + @Override + public void onDestroy() { + // remove runnables in the queue. + mHandler.removeCallbacksAndMessages(null); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing(); + if (mNeedsQSTooltipReshow || isTooltipWindowShowing) { + outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true); + } } void setInteractionListener(ProgressInteractionListener interactionListener) { @@ -91,11 +132,15 @@ class PreviewSizeSeekBarController extends BasePreferenceController implements final int dataSize = mSizeData.getValues().size(); final int initialIndex = mSizeData.getInitialIndex(); + mLastProgress = initialIndex; mSeekBarPreference = screen.findPreference(getPreferenceKey()); mSeekBarPreference.setMax(dataSize - 1); mSeekBarPreference.setProgress(initialIndex); mSeekBarPreference.setContinuousUpdates(true); mSeekBarPreference.setOnSeekBarChangeListener(mSeekBarChangeListener); + if (mNeedsQSTooltipReshow) { + mHandler.post(this::showQuickSettingsTooltipIfNeeded); + } } @Override @@ -108,6 +153,44 @@ class PreviewSizeSeekBarController extends BasePreferenceController implements mInteractionListener.ifPresent(ProgressInteractionListener::onProgressChanged); } + private void onProgressFinalized() { + // Using progress in SeekBarPreference since the progresses in + // SeekBarPreference and seekbar are not always the same. + // See {@link androidx.preference.Preference#callChangeListener(Object)} + int seekBarPreferenceProgress = mSeekBarPreference.getProgress(); + if (seekBarPreferenceProgress != mLastProgress) { + showQuickSettingsTooltipIfNeeded(); + mLastProgress = seekBarPreferenceProgress; + } + } + + private void showQuickSettingsTooltipIfNeeded() { + final ComponentName tileComponentName = getTileComponentName(); + if (tileComponentName == null) { + // Returns if no tile service assigned. + return; + } + + if (!mNeedsQSTooltipReshow && AccessibilityQuickSettingUtils.hasValueInSharedPreferences( + mContext, tileComponentName)) { + // Returns if quick settings tooltip only show once. + return; + } + + mTooltipWindow = new AccessibilityQuickSettingsTooltipWindow(mContext); + mTooltipWindow.setup(getTileTooltipContent(), + R.drawable.accessibility_auto_added_qs_tooltip_illustration); + mTooltipWindow.showAtTopCenter(mSeekBarPreference.getSeekbar()); + AccessibilityQuickSettingUtils.optInValueToSharedPreferences(mContext, tileComponentName); + mNeedsQSTooltipReshow = false; + } + + /** Returns the accessibility Quick Settings tile component name. */ + abstract ComponentName getTileComponentName(); + + /** Returns accessibility Quick Settings tile tooltip content. */ + abstract CharSequence getTileTooltipContent(); + /** * Interface for callbacks when users interact with the seek bar. diff --git a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java index b35a5fe5a24..97a9071066e 100644 --- a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java +++ b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java @@ -16,11 +16,13 @@ package com.android.settings.accessibility; +import static com.android.internal.accessibility.AccessibilityShortcutController.FONT_SIZE_COMPONENT_NAME; import static com.android.settings.accessibility.TextReadingResetController.ResetStateListener; import android.app.Activity; import android.app.Dialog; import android.app.settings.SettingsEnums; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; @@ -156,12 +158,34 @@ public class TextReadingPreferenceFragment extends DashboardFragment { controllers.add(mPreviewController); final PreviewSizeSeekBarController fontSizeController = new PreviewSizeSeekBarController( - context, FONT_SIZE_KEY, fontSizeData); + context, FONT_SIZE_KEY, fontSizeData) { + @Override + ComponentName getTileComponentName() { + return FONT_SIZE_COMPONENT_NAME; + } + + @Override + CharSequence getTileTooltipContent() { + return context.getText( + R.string.accessibility_font_scaling_auto_added_qs_tooltip_content); + } + }; fontSizeController.setInteractionListener(mPreviewController); + getSettingsLifecycle().addObserver(fontSizeController); controllers.add(fontSizeController); final PreviewSizeSeekBarController displaySizeController = new PreviewSizeSeekBarController( - context, DISPLAY_SIZE_KEY, displaySizeData); + context, DISPLAY_SIZE_KEY, displaySizeData) { + @Override + ComponentName getTileComponentName() { + return null; + } + + @Override + CharSequence getTileTooltipContent() { + return null; + } + }; displaySizeController.setInteractionListener(mPreviewController); controllers.add(displaySizeController); diff --git a/src/com/android/settings/applications/appinfo/AppLocaleDetails.java b/src/com/android/settings/applications/appinfo/AppLocaleDetails.java index 1a7793e2ead..6144a73821c 100644 --- a/src/com/android/settings/applications/appinfo/AppLocaleDetails.java +++ b/src/com/android/settings/applications/appinfo/AppLocaleDetails.java @@ -213,7 +213,7 @@ public class AppLocaleDetails extends SettingsPreferenceFragment { if (appLocale == null) { return context.getString(R.string.preference_of_system_locale_summary); } else { - return LocaleHelper.getDisplayName(appLocale, appLocale, true); + return LocaleHelper.getDisplayName(appLocale.stripExtensions(), appLocale, true); } } } diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index e2045f80c27..563c010d692 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -452,6 +452,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll item.registerCallback(this); } mProfileManager.addServiceListener(this); + refresh(); } @Override diff --git a/src/com/android/settings/display/ScreenResolutionFragment.java b/src/com/android/settings/display/ScreenResolutionFragment.java index b40b48f246f..de7d25fefb9 100644 --- a/src/com/android/settings/display/ScreenResolutionFragment.java +++ b/src/com/android/settings/display/ScreenResolutionFragment.java @@ -251,10 +251,10 @@ public class ScreenResolutionFragment extends RadioButtonPickerFragment { if (TextUtils.equals( mScreenResolutionOptions[ScreenResolutionController.HIGHRESOLUTION_IDX], key)) { - preference.setLottieAnimationResId(R.drawable.screen_resolution_1080p); + preference.setLottieAnimationResId(R.drawable.screen_resolution_high); } else if (TextUtils.equals( mScreenResolutionOptions[ScreenResolutionController.FULLRESOLUTION_IDX], key)) { - preference.setLottieAnimationResId(R.drawable.screen_resolution_1440p); + preference.setLottieAnimationResId(R.drawable.screen_resolution_full); } } diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index fac3aafe152..7713e270425 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -29,11 +29,12 @@ import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.ApplicationInfoFlags; import android.content.pm.UserInfo; import android.content.res.Configuration; +import android.net.Uri; import android.os.Bundle; import android.os.Process; -import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; @@ -71,7 +72,6 @@ import com.android.settings.core.CategoryMixin; import com.android.settings.core.FeatureFlags; import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.overlay.FeatureFactory; -import com.android.settings.password.PasswordUtils; import com.android.settings.safetycenter.SafetyCenterManagerWrapper; import com.android.settingslib.Utils; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; @@ -96,6 +96,10 @@ public class SettingsHomepageActivity extends FragmentActivity implements public static final String EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_DATA = "settings_large_screen_deep_link_intent_data"; + // The referrer who fires the initial intent to start the homepage + @VisibleForTesting + static final String EXTRA_INITIAL_REFERRER = "initial_referrer"; + static final int DEFAULT_HIGHLIGHT_MENU_KEY = R.string.menu_key_network; private static final long HOMEPAGE_LOADING_TIMEOUT_MS = 300; @@ -177,12 +181,16 @@ public class SettingsHomepageActivity extends FragmentActivity implements mIsEmbeddingActivityEnabled = ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this); if (mIsEmbeddingActivityEnabled) { final UserManager um = getSystemService(UserManager.class); - final UserInfo userInfo = um.getUserInfo(getUser().getIdentifier()); + final UserInfo userInfo = um.getUserInfo(getUserId()); if (userInfo.isManagedProfile()) { final Intent intent = new Intent(getIntent()) - .setClass(this, DeepLinkHomepageActivityInternal.class) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) - .putExtra(EXTRA_USER_HANDLE, getUser()); + .putExtra(EXTRA_USER_HANDLE, getUser()) + .putExtra(EXTRA_INITIAL_REFERRER, getCurrentReferrer()); + if (TextUtils.equals(intent.getAction(), ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY) + && this instanceof DeepLinkHomepageActivity) { + intent.setClass(this, DeepLinkHomepageActivityInternal.class); + } intent.removeFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivityAsUser(intent, um.getProfileParent(userInfo.id).getUserHandle()); finish(); @@ -471,7 +479,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements return; } - ActivityInfo targetActivityInfo = null; + ActivityInfo targetActivityInfo; try { targetActivityInfo = getPackageManager().getActivityInfo(targetComponentName, /* flags= */ 0); @@ -481,23 +489,29 @@ public class SettingsHomepageActivity extends FragmentActivity implements return; } - int callingUid = -1; - try { - callingUid = ActivityManager.getService().getLaunchedFromUid(getActivityToken()); - } catch (RemoteException re) { - Log.e(TAG, "Not able to get callingUid: " + re); - finish(); - return; + UserHandle user = intent.getParcelableExtra(EXTRA_USER_HANDLE, UserHandle.class); + String caller = getInitialReferrer(); + int callerUid = -1; + if (caller != null) { + try { + callerUid = getPackageManager().getApplicationInfoAsUser(caller, + ApplicationInfoFlags.of(/* flags= */ 0), + user != null ? user.getIdentifier() : getUserId()).uid; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Not able to get callerUid: " + e); + finish(); + return; + } } - if (!hasPrivilegedAccess(callingUid, targetActivityInfo)) { + if (!hasPrivilegedAccess(caller, callerUid, targetActivityInfo.packageName)) { if (!targetActivityInfo.exported) { Log.e(TAG, "Target Activity is not exported"); finish(); return; } - if (!isCallingAppPermitted(targetActivityInfo.permission)) { + if (!isCallingAppPermitted(targetActivityInfo.permission, callerUid)) { Log.e(TAG, "Calling app must have the permission of deep link Activity"); finish(); return; @@ -510,7 +524,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); if (targetIntent.getData() != null && uriPermissionFlags != 0 - && checkUriPermission(targetIntent.getData(), /* pid= */ -1, callingUid, + && checkUriPermission(targetIntent.getData(), /* pid= */ -1, callerUid, uriPermissionFlags) == PackageManager.PERMISSION_DENIED) { Log.e(TAG, "Calling app must have the permission to access Uri and grant permission"); finish(); @@ -547,7 +561,6 @@ public class SettingsHomepageActivity extends FragmentActivity implements SplitRule.FinishBehavior.ALWAYS, true /* clearTop */); - final UserHandle user = intent.getParcelableExtra(EXTRA_USER_HANDLE, UserHandle.class); if (user != null) { startActivityAsUser(targetIntent, user); } else { @@ -555,31 +568,30 @@ public class SettingsHomepageActivity extends FragmentActivity implements } } - // Check if calling app has privileged access to launch Activity of activityInfo. - private boolean hasPrivilegedAccess(int callingUid, ActivityInfo activityInfo) { - if (TextUtils.equals(PasswordUtils.getCallingAppPackageName(getActivityToken()), - getPackageName())) { + // Check if the caller has privileged access to launch the target page. + private boolean hasPrivilegedAccess(String callerPkg, int callerUid, String targetPackage) { + if (TextUtils.equals(callerPkg, getPackageName())) { return true; } int targetUid = -1; try { - targetUid = getPackageManager().getApplicationInfo(activityInfo.packageName, - /* flags= */ 0).uid; - } catch (PackageManager.NameNotFoundException nnfe) { - Log.e(TAG, "Not able to get targetUid: " + nnfe); + targetUid = getPackageManager().getApplicationInfo(targetPackage, + ApplicationInfoFlags.of(/* flags= */ 0)).uid; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Not able to get targetUid: " + e); return false; } // When activityInfo.exported is false, Activity still can be launched if applications have // the same user ID. - if (UserHandle.isSameApp(callingUid, targetUid)) { + if (UserHandle.isSameApp(callerUid, targetUid)) { return true; } // When activityInfo.exported is false, Activity still can be launched if calling app has // root or system privilege. - int callingAppId = UserHandle.getAppId(callingUid); + int callingAppId = UserHandle.getAppId(callerUid); if (callingAppId == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID) { return true; } @@ -588,9 +600,31 @@ public class SettingsHomepageActivity extends FragmentActivity implements } @VisibleForTesting - boolean isCallingAppPermitted(String permission) { - return TextUtils.isEmpty(permission) || PasswordUtils.isCallingAppPermitted( - this, getActivityToken(), permission); + String getInitialReferrer() { + String referrer = getCurrentReferrer(); + if (!TextUtils.equals(referrer, getPackageName())) { + return referrer; + } + + String initialReferrer = getIntent().getStringExtra(EXTRA_INITIAL_REFERRER); + return TextUtils.isEmpty(initialReferrer) ? referrer : initialReferrer; + } + + @VisibleForTesting + String getCurrentReferrer() { + Intent intent = getIntent(); + // Clear extras to get the real referrer + intent.removeExtra(Intent.EXTRA_REFERRER); + intent.removeExtra(Intent.EXTRA_REFERRER_NAME); + Uri referrer = getReferrer(); + return referrer != null ? referrer.getHost() : null; + } + + @VisibleForTesting + boolean isCallingAppPermitted(String permission, int callerUid) { + return TextUtils.isEmpty(permission) + || checkPermission(permission, /* pid= */ -1, callerUid) + == PackageManager.PERMISSION_GRANTED; } private String getHighlightMenuKey() { diff --git a/src/com/android/settings/widget/LabeledSeekBarPreference.java b/src/com/android/settings/widget/LabeledSeekBarPreference.java index 5d1011634c7..6300bd3b318 100644 --- a/src/com/android/settings/widget/LabeledSeekBarPreference.java +++ b/src/com/android/settings/widget/LabeledSeekBarPreference.java @@ -63,6 +63,8 @@ public class LabeledSeekBarPreference extends SeekBarPreference { private OnPreferenceChangeListener mStopListener; private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener; + private SeekBar mSeekBar; + public LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { @@ -104,6 +106,10 @@ public class LabeledSeekBarPreference extends SeekBarPreference { com.android.internal.R.attr.seekBarPreferenceStyle), 0); } + public SeekBar getSeekbar() { + return mSeekBar; + } + @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); @@ -133,19 +139,19 @@ public class LabeledSeekBarPreference extends SeekBarPreference { final boolean isValidTextResIdExist = mTextStartId > 0 || mTextEndId > 0; labelFrame.setVisibility(isValidTextResIdExist ? View.VISIBLE : View.GONE); - final SeekBar seekBar = (SeekBar) holder.findViewById(com.android.internal.R.id.seekbar); + mSeekBar = (SeekBar) holder.findViewById(com.android.internal.R.id.seekbar); if (mTickMarkId != 0) { final Drawable tickMark = getContext().getDrawable(mTickMarkId); - seekBar.setTickMark(tickMark); + mSeekBar.setTickMark(tickMark); } final ViewGroup iconStartFrame = (ViewGroup) holder.findViewById(R.id.icon_start_frame); final ImageView iconStartView = (ImageView) holder.findViewById(R.id.icon_start); - updateIconStartIfNeeded(iconStartFrame, iconStartView, seekBar); + updateIconStartIfNeeded(iconStartFrame, iconStartView, mSeekBar); final ViewGroup iconEndFrame = (ViewGroup) holder.findViewById(R.id.icon_end_frame); final ImageView iconEndView = (ImageView) holder.findViewById(R.id.icon_end); - updateIconEndIfNeeded(iconEndFrame, iconEndView, seekBar); + updateIconEndIfNeeded(iconEndFrame, iconEndView, mSeekBar); } public void setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener) { diff --git a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java index 52ccb374ca6..6b0f5c00413 100644 --- a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java @@ -16,29 +16,45 @@ package com.android.settings.accessibility; +import static com.android.internal.accessibility.AccessibilityShortcutController.FONT_SIZE_COMPONENT_NAME; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.widget.PopupWindow; import android.widget.SeekBar; +import androidx.preference.PreferenceManager; import androidx.preference.PreferenceScreen; +import androidx.preference.PreferenceViewHolder; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.R; +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.testutils.shadow.ShadowFragment; import com.android.settings.testutils.shadow.ShadowInteractionJankMonitor; import com.android.settings.widget.LabeledSeekBarPreference; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowApplication; /** * Tests for {@link PreviewSizeSeekBarController}. @@ -47,30 +63,67 @@ import org.robolectric.annotation.Config; @Config(shadows = {ShadowInteractionJankMonitor.class}) public class PreviewSizeSeekBarControllerTest { private static final String FONT_SIZE_KEY = "font_size"; + private static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow"; + @Spy private final Context mContext = ApplicationProvider.getApplicationContext(); private PreviewSizeSeekBarController mSeekBarController; private FontSizeData mFontSizeData; private LabeledSeekBarPreference mSeekBarPreference; - @Mock private PreferenceScreen mPreferenceScreen; + private TestFragment mFragment; + private PreferenceViewHolder mHolder; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceManager mPreferenceManager; @Mock private PreviewSizeSeekBarController.ProgressInteractionListener mInteractionListener; + private static PopupWindow getLatestPopupWindow() { + final ShadowApplication shadowApplication = + Shadow.extract(ApplicationProvider.getApplicationContext()); + return shadowApplication.getLatestPopupWindow(); + } + @Before public void setUp() { MockitoAnnotations.initMocks(this); - mFontSizeData = new FontSizeData(mContext); - - mSeekBarController = - new PreviewSizeSeekBarController(mContext, FONT_SIZE_KEY, mFontSizeData); - + mContext.setTheme(R.style.Theme_AppCompat); + mFragment = spy(new TestFragment()); + when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager); + when(mFragment.getPreferenceManager().getContext()).thenReturn(mContext); + when(mFragment.getContext()).thenReturn(mContext); + mPreferenceScreen = spy(new PreferenceScreen(mContext, /* attrs= */ null)); + when(mPreferenceScreen.getPreferenceManager()).thenReturn(mPreferenceManager); + doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); mSeekBarPreference = spy(new LabeledSeekBarPreference(mContext, /* attrs= */ null)); + mSeekBarPreference.setKey(FONT_SIZE_KEY); + + LayoutInflater inflater = LayoutInflater.from(mContext); + mHolder = PreferenceViewHolder.createInstanceForTests(inflater.inflate( + R.layout.preference_labeled_slider, null)); + mSeekBarPreference.onBindViewHolder(mHolder); + when(mPreferenceScreen.findPreference(anyString())).thenReturn(mSeekBarPreference); + mFontSizeData = new FontSizeData(mContext); + mSeekBarController = + new PreviewSizeSeekBarController(mContext, FONT_SIZE_KEY, mFontSizeData) { + @Override + ComponentName getTileComponentName() { + return FONT_SIZE_COMPONENT_NAME; + } + + @Override + CharSequence getTileTooltipContent() { + return mContext.getText( + R.string.accessibility_font_scaling_auto_added_qs_tooltip_content); + } + }; mSeekBarController.setInteractionListener(mInteractionListener); + when(mPreferenceScreen.findPreference(mSeekBarController.getPreferenceKey())).thenReturn( + mSeekBarPreference); } @Test @@ -123,4 +176,64 @@ public class PreviewSizeSeekBarControllerTest { verify(mInteractionListener).notifyPreferenceChanged(); } + + @Test + public void onProgressChanged_showTooltipView() { + mSeekBarController.displayPreference(mPreferenceScreen); + + // Simulate changing the progress for the first time + int newProgress = (mSeekBarPreference.getProgress() != 0) ? 0 : mSeekBarPreference.getMax(); + mSeekBarPreference.setProgress(newProgress); + mSeekBarPreference.onProgressChanged(new SeekBar(mContext), + newProgress, + /* fromUser= */ false); + + assertThat(getLatestPopupWindow().isShowing()).isTrue(); + } + + @Test + public void onProgressChanged_tooltipViewHasBeenShown_notShowTooltipView() { + mSeekBarController.displayPreference(mPreferenceScreen); + // Simulate changing the progress for the first time + int newProgress = (mSeekBarPreference.getProgress() != 0) ? 0 : mSeekBarPreference.getMax(); + mSeekBarPreference.setProgress(newProgress); + mSeekBarPreference.onProgressChanged(new SeekBar(mContext), + newProgress, + /* fromUser= */ false); + getLatestPopupWindow().dismiss(); + + // Simulate progress changing for the second time + newProgress = (mSeekBarPreference.getProgress() != 0) ? 0 : mSeekBarPreference.getMax(); + mSeekBarPreference.setProgress(newProgress); + mSeekBarPreference.onProgressChanged(new SeekBar(mContext), + newProgress, + /* fromUser= */ false); + + assertThat(getLatestPopupWindow().isShowing()).isFalse(); + } + + @Test + @Config(shadows = ShadowFragment.class) + public void restoreValueFromSavedInstanceState_showTooltipView() { + final Bundle savedInstanceState = new Bundle(); + savedInstanceState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true); + mSeekBarController.onCreate(savedInstanceState); + + mSeekBarController.displayPreference(mPreferenceScreen); + + assertThat(getLatestPopupWindow().isShowing()).isTrue(); + } + + private static class TestFragment extends SettingsPreferenceFragment { + + @Override + protected boolean shouldSkipForInitialSUW() { + return false; + } + + @Override + public int getMetricsCategory() { + return 0; + } + } } diff --git a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java index 60f153cf233..f1d6796d775 100644 --- a/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java +++ b/tests/robotests/src/com/android/settings/homepage/SettingsHomepageActivityTest.java @@ -20,8 +20,13 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -30,6 +35,8 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; import android.view.View; import android.view.Window; @@ -215,29 +222,89 @@ public class SettingsHomepageActivityTest { } @Test - @Config(shadows = {ShadowPasswordUtils.class}) + public void getInitialReferrer_differentPackage_returnCurrentReferrer() { + SettingsHomepageActivity activity = + spy(Robolectric.buildActivity(SettingsHomepageActivity.class).get()); + String referrer = "com.abc"; + doReturn(referrer).when(activity).getCurrentReferrer(); + + assertEquals(activity.getInitialReferrer(), referrer); + } + + @Test + public void getInitialReferrer_noReferrerExtra_returnCurrentReferrer() { + SettingsHomepageActivity activity = + spy(Robolectric.buildActivity(SettingsHomepageActivity.class).get()); + String referrer = activity.getPackageName(); + doReturn(referrer).when(activity).getCurrentReferrer(); + + assertEquals(activity.getInitialReferrer(), referrer); + } + + @Test + public void getInitialReferrer_hasReferrerExtra_returnGivenReferrer() { + SettingsHomepageActivity activity = + spy(Robolectric.buildActivity(SettingsHomepageActivity.class).get()); + doReturn(activity.getPackageName()).when(activity).getCurrentReferrer(); + String referrer = "com.abc"; + activity.setIntent(new Intent().putExtra(SettingsHomepageActivity.EXTRA_INITIAL_REFERRER, + referrer)); + + assertEquals(activity.getInitialReferrer(), referrer); + } + + @Test + public void getCurrentReferrer_hasReferrerExtra_shouldNotEqual() { + String referrer = "com.abc"; + Uri uri = new Uri.Builder().scheme("android-app").authority(referrer).build(); + SettingsHomepageActivity activity = + spy(Robolectric.buildActivity(SettingsHomepageActivity.class).get()); + activity.setIntent(new Intent().putExtra(Intent.EXTRA_REFERRER, uri)); + + assertNotEquals(activity.getCurrentReferrer(), referrer); + } + + @Test + public void getCurrentReferrer_hasReferrerNameExtra_shouldNotEqual() { + String referrer = "com.abc"; + SettingsHomepageActivity activity = + spy(Robolectric.buildActivity(SettingsHomepageActivity.class).get()); + activity.setIntent(new Intent().putExtra(Intent.EXTRA_REFERRER_NAME, referrer)); + + assertNotEquals(activity.getCurrentReferrer(), referrer); + } + + @Test public void isCallingAppPermitted_emptyPermission_returnTrue() { - SettingsHomepageActivity homepageActivity = spy(new SettingsHomepageActivity()); + SettingsHomepageActivity activity = + spy(Robolectric.buildActivity(SettingsHomepageActivity.class).get()); + doReturn(PackageManager.PERMISSION_DENIED).when(activity) + .checkPermission(anyString(), anyInt(), anyInt()); - assertTrue(homepageActivity.isCallingAppPermitted("")); + assertTrue(activity.isCallingAppPermitted("", 1000)); } @Test - @Config(shadows = {ShadowPasswordUtils.class}) - public void isCallingAppPermitted_noGrantedPermission_returnFalse() { - SettingsHomepageActivity homepageActivity = spy(new SettingsHomepageActivity()); + public void isCallingAppPermitted_notGrantedPermission_returnFalse() { + SettingsHomepageActivity activity = + spy(Robolectric.buildActivity(SettingsHomepageActivity.class).get()); + doReturn(PackageManager.PERMISSION_DENIED).when(activity) + .checkPermission(anyString(), anyInt(), anyInt()); - assertFalse(homepageActivity.isCallingAppPermitted("android.permission.TEST")); + assertFalse(activity.isCallingAppPermitted("android.permission.TEST", 1000)); } @Test - @Config(shadows = {ShadowPasswordUtils.class}) public void isCallingAppPermitted_grantedPermission_returnTrue() { - SettingsHomepageActivity homepageActivity = spy(new SettingsHomepageActivity()); + SettingsHomepageActivity activity = + spy(Robolectric.buildActivity(SettingsHomepageActivity.class).get()); String permission = "android.permission.TEST"; - ShadowPasswordUtils.addGrantedPermission(permission); + doReturn(PackageManager.PERMISSION_DENIED).when(activity) + .checkPermission(anyString(), anyInt(), anyInt()); + doReturn(PackageManager.PERMISSION_GRANTED).when(activity) + .checkPermission(eq(permission), anyInt(), eq(1000)); - assertTrue(homepageActivity.isCallingAppPermitted(permission)); + assertTrue(activity.isCallingAppPermitted(permission, 1000)); } @Implements(SuggestionFeatureProviderImpl.class)