diff options
author | Ryan Lin <ryanlwlin@google.com> | 2020-11-19 08:37:51 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-11-19 08:37:51 +0000 |
commit | 00bca78ece33c5aa3773fbf7bd9bbf3dec5a1a9c (patch) | |
tree | 10a91da392e4a4bdbfa85e7817a709d3645124a6 | |
parent | 62b922614c4a3651d2be8866a6e9edb7fc5eb501 (diff) | |
parent | 3badad790dedeae98aa2030d869d0e6829a923f9 (diff) |
Merge "Show notification to promote window magnification mode available"
9 files changed, 482 insertions, 5 deletions
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index 41be5c493d95..2237efc9e2b6 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -57,6 +57,7 @@ public class SystemNotificationChannels { public static String HEAVY_WEIGHT_APP = "HEAVY_WEIGHT_APP"; public static String SYSTEM_CHANGES = "SYSTEM_CHANGES"; public static String DO_NOT_DISTURB = "DO_NOT_DISTURB"; + public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION"; public static void createAll(Context context) { final NotificationManager nm = context.getSystemService(NotificationManager.class); @@ -191,6 +192,13 @@ public class SystemNotificationChannels { NotificationManager.IMPORTANCE_LOW); channelsList.add(dndChanges); + final NotificationChannel newFeaturePrompt = new NotificationChannel( + ACCESSIBILITY_MAGNIFICATION, + context.getString(R.string.notification_channel_accessibility_magnification), + NotificationManager.IMPORTANCE_HIGH); + newFeaturePrompt.setBlockable(true); + channelsList.add(newFeaturePrompt); + nm.createNotificationChannels(channelsList); } diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 8eb0853fda24..be6b6b15fe4d 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -717,6 +717,10 @@ [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6665375982962336520] --> <string name="notification_channel_foreground_service">Apps consuming battery</string> + <!-- Text shown when viewing channel settings for notifications related to accessibility + magnification. [CHAR_LIMIT=NONE]--> + <string name="notification_channel_accessibility_magnification">Magnification</string> + <!-- Label for foreground service notification when one app is running. [CHAR LIMIT=NONE BACKUP_MESSAGE_ID=6826789589341671842] --> <string name="foreground_service_app_in_background"><xliff:g id="app_name">%1$s</xliff:g> is @@ -5767,4 +5771,16 @@ ul.</string> <string name="config_pdp_reject_service_not_subscribed"></string> <!-- pdp data reject dialog string for cause 55 (MULTI_CONN_TO_SAME_PDN_NOT_ALLOWED) [CHAR LIMIT=100] --> <string name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed"></string> + + <!-- Window magnification prompt related string. --> + + <!-- Notification title to prompt the user that new magnification feature is available. [CHAR LIMIT=50] --> + <string name="window_magnification_prompt_title">New: Window Magnifier</string> + <!-- Notification content to prompt the user that new magnification feature is available. [CHAR LIMIT=50] --> + <string name="window_magnification_prompt_content">You can now magnify some or all of your screen</string> + <!-- Notification action to bring the user to magnification settings page. [CHAR LIMIT=50] --> + <string name="turn_on_magnification_settings_action">Turn on in Settings</string> + <!-- Notification action to dismiss. [CHAR LIMIT=50] --> + <string name="dismiss_action">Dismiss</string> + </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 23733af4455e..752bb5b37a30 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3458,6 +3458,7 @@ <java-symbol type="string" name="notification_channel_heavy_weight_app" /> <java-symbol type="string" name="notification_channel_system_changes" /> <java-symbol type="string" name="notification_channel_do_not_disturb" /> + <java-symbol type="string" name="notification_channel_accessibility_magnification" /> <java-symbol type="string" name="config_defaultAutofillService" /> <java-symbol type="string" name="config_defaultTextClassifierPackage" /> <java-symbol type="string" name="config_defaultWellbeingPackage" /> @@ -4096,4 +4097,10 @@ <java-symbol type="dimen" name="config_taskLetterboxAspectRatio" /> <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" /> + + <!-- Window magnification prompt --> + <java-symbol type="string" name="window_magnification_prompt_title" /> + <java-symbol type="string" name="window_magnification_prompt_content" /> + <java-symbol type="string" name="turn_on_magnification_settings_action" /> + <java-symbol type="string" name="dismiss_action" /> </resources> diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 15bd4dc66ccc..ae024ff6d043 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -332,5 +332,9 @@ message SystemMessage { // Notify the user that the admin suspended personal apps on the device. // Package: android NOTE_PERSONAL_APPS_SUSPENDED = 1003; + + // Notify the user that window magnification is available. + // package: android + NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE = 1004; } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index 857ac6ae62a9..be7643ecbd4e 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -37,6 +37,7 @@ import com.android.server.accessibility.gestures.TouchExplorer; import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler; import com.android.server.accessibility.magnification.MagnificationGestureHandler; import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler; +import com.android.server.accessibility.magnification.WindowMagnificationPromptController; import com.android.server.policy.WindowManagerPolicy; import java.util.ArrayList; @@ -561,8 +562,9 @@ class AccessibilityInputFilter extends InputFilter implements EventStreamTransfo detectControlGestures, triggerable, displayId); } else { magnificationGestureHandler = new FullScreenMagnificationGestureHandler(displayContext, - mAms.getFullScreenMagnificationController(), mAms::onMagnificationScaleChanged, - detectControlGestures, triggerable, displayId); + mAms.getFullScreenMagnificationController(), + mAms::onMagnificationScaleChanged, detectControlGestures, triggerable, + new WindowMagnificationPromptController(displayContext, mUserId), displayId); } return magnificationGestureHandler; } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index d50e9d720861..efb9d87a0bfd 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -137,6 +137,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @VisibleForTesting final ViewportDraggingState mViewportDraggingState; private final ScreenStateReceiver mScreenStateReceiver; + private final WindowMagnificationPromptController mPromptController; /** * {@code true} if this detector should detect and respond to triple-tap @@ -178,6 +179,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH ScaleChangedListener listener, boolean detectTripleTap, boolean detectShortcutTrigger, + @NonNull WindowMagnificationPromptController promptController, int displayId) { super(listener); if (DEBUG_ALL) { @@ -186,6 +188,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH + ", detectShortcutTrigger = " + detectShortcutTrigger + ")"); } mFullScreenMagnificationController = fullScreenMagnificationController; + mPromptController = promptController; mDisplayId = displayId; mDelegatingState = new DelegatingState(); @@ -195,7 +198,6 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mDetectTripleTap = detectTripleTap; mDetectShortcutTrigger = detectShortcutTrigger; - if (mDetectShortcutTrigger) { mScreenStateReceiver = new ScreenStateReceiver(context, this); mScreenStateReceiver.register(); @@ -264,6 +266,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (mScreenStateReceiver != null) { mScreenStateReceiver.unregister(); } + mPromptController.onDestroy(); // Check if need to reset when MagnificationGestureHandler is the last magnifying service. mFullScreenMagnificationController.resetAllIfNeeded( AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID); @@ -278,6 +281,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (wasMagnifying) { clearAndTransitionToStateDetecting(); } else { + mPromptController.showNotificationIfNeeded(); mDetectingState.toggleShortcutTriggered(); } } @@ -950,6 +954,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH if (mFullScreenMagnificationController.isMagnifying(mDisplayId)) { zoomOff(); } else { + mPromptController.showNotificationIfNeeded(); zoomOn(up.getX(), up.getY()); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java new file mode 100644 index 000000000000..721220729a33 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationPromptController.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2020 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.server.accessibility.magnification; + +import static android.provider.Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT; + +import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME; +import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE; + +import android.Manifest; +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.app.ActivityOptions; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.graphics.drawable.Icon; +import android.net.Uri; +import android.os.Bundle; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.notification.SystemNotificationChannels; + +/** + * A class to show notification to prompt the user that this feature is available. + */ +public class WindowMagnificationPromptController { + + private static final Uri MAGNIFICATION_WINDOW_MODE_PROMPT_URI = Settings.Secure.getUriFor( + ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT); + @VisibleForTesting + static final String ACTION_DISMISS = + "com.android.server.accessibility.magnification.action.DISMISS"; + @VisibleForTesting + static final String ACTION_TURN_ON_IN_SETTINGS = + "com.android.server.accessibility.magnification.action.TURN_ON_IN_SETTINGS"; + private final Context mContext; + private final NotificationManager mNotificationManager; + private final ContentObserver mContentObserver; + private final int mUserId; + @VisibleForTesting + BroadcastReceiver mNotificationActionReceiver; + + private boolean mNeedToShowNotification; + + @MainThread + public WindowMagnificationPromptController(@NonNull Context context, int userId) { + mContext = context; + mNotificationManager = context.getSystemService(NotificationManager.class); + mUserId = userId; + mContentObserver = new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + onPromptSettingsValueChanged(); + } + }; + context.getContentResolver().registerContentObserver(MAGNIFICATION_WINDOW_MODE_PROMPT_URI, + false, mContentObserver, mUserId); + mNeedToShowNotification = isWindowMagnificationPromptEnabled(); + } + + @VisibleForTesting + protected void onPromptSettingsValueChanged() { + final boolean needToShowNotification = isWindowMagnificationPromptEnabled(); + if (mNeedToShowNotification == needToShowNotification) { + return; + } + mNeedToShowNotification = needToShowNotification; + if (!mNeedToShowNotification) { + unregisterReceiverIfNeeded(); + mNotificationManager.cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE); + } + } + + /** + * Shows the prompt notification that could bring users to magnification settings if necessary. + */ + @MainThread + void showNotificationIfNeeded() { + if (!mNeedToShowNotification) return; + + final Notification.Builder notificationBuilder = new Notification.Builder(mContext, + SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION); + notificationBuilder.setSmallIcon(R.drawable.ic_settings_24dp) + .setContentTitle(mContext.getString(R.string.window_magnification_prompt_title)) + .setContentText(mContext.getString(R.string.window_magnification_prompt_content)) + .setLargeIcon(Icon.createWithResource(mContext, + R.drawable.ic_accessibility_magnification)) + .setTicker(mContext.getString(R.string.window_magnification_prompt_title)) + .setOnlyAlertOnce(true) + .setDeleteIntent(createPendingIntent(ACTION_DISMISS)) + .setContentIntent(createPendingIntent(ACTION_TURN_ON_IN_SETTINGS)) + .setActions(buildTurnOnAction(), buildDismissAction()); + mNotificationManager.notify(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE, + notificationBuilder.build()); + registerReceiverIfNeeded(); + } + + /** + * Called when this object is not used anymore to release resources if necessary. + */ + @VisibleForTesting + @MainThread + public void onDestroy() { + dismissNotification(); + mContext.getContentResolver().unregisterContentObserver(mContentObserver); + } + + private boolean isWindowMagnificationPromptEnabled() { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, 0, mUserId) == 1; + } + + private Notification.Action buildTurnOnAction() { + return new Notification.Action.Builder(null, + mContext.getString(R.string.turn_on_magnification_settings_action), + createPendingIntent(ACTION_TURN_ON_IN_SETTINGS)).build(); + } + + private Notification.Action buildDismissAction() { + return new Notification.Action.Builder(null, mContext.getString(R.string.dismiss_action), + createPendingIntent(ACTION_DISMISS)).build(); + } + + private PendingIntent createPendingIntent(String action) { + final Intent intent = new Intent(action); + intent.setPackage(mContext.getPackageName()); + return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE); + } + + private void registerReceiverIfNeeded() { + if (mNotificationActionReceiver != null) { + return; + } + mNotificationActionReceiver = new NotificationActionReceiver(); + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ACTION_DISMISS); + intentFilter.addAction(ACTION_TURN_ON_IN_SETTINGS); + mContext.registerReceiver(mNotificationActionReceiver, intentFilter, + Manifest.permission.MANAGE_ACCESSIBILITY, null); + } + + private void launchMagnificationSettings() { + final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra(Intent.EXTRA_COMPONENT_NAME, + MAGNIFICATION_COMPONENT_NAME.flattenToShortString()); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId( + mContext.getDisplayId()).toBundle(); + mContext.startActivityAsUser(intent, bundle, UserHandle.of(mUserId)); + mContext.getSystemService(StatusBarManager.class).collapsePanels(); + } + + private void dismissNotification() { + unregisterReceiverIfNeeded(); + mNotificationManager.cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE); + } + + private void unregisterReceiverIfNeeded() { + if (mNotificationActionReceiver == null) { + return; + } + mContext.unregisterReceiver(mNotificationActionReceiver); + mNotificationActionReceiver = null; + } + + private class NotificationActionReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (TextUtils.isEmpty(action)) return; + + mNeedToShowNotification = false; + Settings.Secure.putIntForUser(mContext.getContentResolver(), + ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, 0, mUserId); + + if (ACTION_TURN_ON_IN_SETTINGS.equals(action)) { + launchMagnificationSettings(); + dismissNotification(); + } else if (ACTION_DISMISS.equals(action)) { + dismissNotification(); + } + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 1cdd873860b8..e43a002806ee 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -66,7 +66,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; - import java.util.ArrayList; import java.util.List; import java.util.function.IntConsumer; @@ -129,6 +128,8 @@ public class FullScreenMagnificationGestureHandlerTest { ScaleChangedListener mMockScaleChangedListener; @Mock MagnificationRequestObserver mMagnificationRequestObserver; + @Mock + WindowMagnificationPromptController mWindowMagnificationPromptController; private OffsettableClock mClock; private FullScreenMagnificationGestureHandler mMgh; @@ -170,7 +171,9 @@ public class FullScreenMagnificationGestureHandlerTest { @After public void tearDown() { + mMgh.onDestroy(); mFullScreenMagnificationController.unregister(DISPLAY_0); + verify(mWindowMagnificationPromptController).onDestroy(); } @NonNull @@ -178,7 +181,8 @@ public class FullScreenMagnificationGestureHandlerTest { boolean detectShortcutTrigger) { FullScreenMagnificationGestureHandler h = new FullScreenMagnificationGestureHandler( mContext, mFullScreenMagnificationController, mMockScaleChangedListener, - detectTripleTap, detectShortcutTrigger, DISPLAY_0); + detectTripleTap, detectShortcutTrigger, mWindowMagnificationPromptController, + DISPLAY_0); mHandler = new TestHandler(h.mDetectingState, mClock) { @Override protected String messageToString(Message m) { @@ -434,6 +438,20 @@ public class FullScreenMagnificationGestureHandlerTest { returnToNormalFrom(STATE_PANNING); } + @Test + public void testZoomedWithTripleTap_invokeShowWindowPromptAction() { + goFromStateIdleTo(STATE_ZOOMED); + + verify(mWindowMagnificationPromptController).showNotificationIfNeeded(); + } + + @Test + public void testShortcutTriggered_invokeShowWindowPromptAction() { + goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); + + verify(mWindowMagnificationPromptController).showNotificationIfNeeded(); + } + private void assertActionsInOrder(List<MotionEvent> actualEvents, List<Integer> expectedActions) { assertTrue(actualEvents.size() == expectedActions.size()); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java new file mode 100644 index 000000000000..5fd28f57c7c3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationPromptControllerTest.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2020 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.server.accessibility.magnification; + +import static android.provider.Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT; + +import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE; +import static com.android.server.accessibility.magnification.WindowMagnificationPromptController.ACTION_TURN_ON_IN_SETTINGS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.StatusBarManager; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; +import android.testing.TestableContext; + +import androidx.test.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link WindowMagnificationPromptController}. + */ +public class WindowMagnificationPromptControllerTest { + + private static final int TEST_USER = 0; + + @Mock + private NotificationManager mNotificationManager; + @Mock + private StatusBarManager mStatusBarManager; + @Rule + public A11yTestableContext mTestableContext = new A11yTestableContext( + InstrumentationRegistry.getContext()); + private ContentResolver mResolver = mTestableContext.getContentResolver(); + private WindowMagnificationPromptController mWindowMagnificationPromptController; + private BroadcastReceiver mReceiver; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mTestableContext.addMockSystemService(NotificationManager.class, mNotificationManager); + mTestableContext.addMockSystemService(StatusBarManager.class, mStatusBarManager); + setWindowMagnificationPromptSettings(true); + mWindowMagnificationPromptController = new WindowMagnificationPromptController( + mTestableContext, TEST_USER); + } + + @After + public void tearDown() throws Exception { + mWindowMagnificationPromptController.onDestroy(); + } + + @Test + public void showNotificationIfNeeded_promptSettingsIsOn_showNotification() { + mWindowMagnificationPromptController.showNotificationIfNeeded(); + + verify(mNotificationManager).notify(eq(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE), any( + Notification.class)); + } + + @Test + public void tapTurnOnAction_isShown_cancelNotificationAndLaunchMagnificationSettings() { + showNotificationAndAssert(); + + final Intent intent = new Intent(ACTION_TURN_ON_IN_SETTINGS); + mReceiver.onReceive(mTestableContext, intent); + + verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE); + verifyLaunchMagnificationSettings(); + } + + @Test + public void tapTurnOnAction_isShown_settingsValueIsFalseAndUnregisterReceiver() { + showNotificationAndAssert(); + + final Intent intent = new Intent(ACTION_TURN_ON_IN_SETTINGS); + mReceiver.onReceive(mTestableContext, intent); + + assertThat(Settings.Secure.getInt(mResolver, ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, + -1)).isEqualTo(0); + verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver); + } + + @Test + public void tapDismissAction_isShown_cancelNotificationAndUnregisterReceiver() { + showNotificationAndAssert(); + + final Intent intent = new Intent(WindowMagnificationPromptController.ACTION_DISMISS); + mReceiver.onReceive(mTestableContext, intent); + + verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE); + verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver); + } + + @Test + public void promptSettingsChangeToFalse_isShown_cancelNotificationAndUnregisterReceiver() { + showNotificationAndAssert(); + + setWindowMagnificationPromptSettings(false); + + verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE); + verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver); + } + + @Test + public void onDestroy_isShown_cancelNotificationAndUnregisterReceiver() { + showNotificationAndAssert(); + + mWindowMagnificationPromptController.onDestroy(); + + verify(mNotificationManager).cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE); + verify(mTestableContext.getSpyContext()).unregisterReceiver(mReceiver); + } + + private void verifyLaunchMagnificationSettings() { + final ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + final ArgumentCaptor<UserHandle> userHandleCaptor = ArgumentCaptor.forClass( + UserHandle.class); + verify(mTestableContext.getSpyContext()).startActivityAsUser(intentCaptor.capture(), + bundleCaptor.capture(), userHandleCaptor.capture()); + assertThat(intentCaptor.getValue().getAction()).isEqualTo( + Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS); + assertThat(userHandleCaptor.getValue().getIdentifier()).isEqualTo(TEST_USER); + verify(mStatusBarManager).collapsePanels(); + } + + private void showNotificationAndAssert() { + mWindowMagnificationPromptController.showNotificationIfNeeded(); + mReceiver = mWindowMagnificationPromptController.mNotificationActionReceiver; + assertThat(mReceiver).isNotNull(); + } + + private void setWindowMagnificationPromptSettings(boolean enable) { + Settings.Secure.putIntForUser(mResolver, ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, + enable ? 1 : 0, TEST_USER); + if (mWindowMagnificationPromptController != null) { + mWindowMagnificationPromptController.onPromptSettingsValueChanged(); + } + } + + private class A11yTestableContext extends TestableContext { + + private Context mSpyContext; + + A11yTestableContext(Context base) { + super(base); + mSpyContext = Mockito.mock(Context.class); + } + + @Override + public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) { + mSpyContext.startActivityAsUser(intent, options, user); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler) { + return mSpyContext.registerReceiver(receiver, filter, broadcastPermission, scheduler); + } + + @Override + public void unregisterReceiver(BroadcastReceiver receiver) { + mSpyContext.unregisterReceiver(receiver); + } + + Context getSpyContext() { + return mSpyContext; + } + } +} |