Show notification to promote window magnification mode available
The magnification mode is full-screen when the migrating deivce turns on
magnification settings.
We show a notification prompt the user that new window magnifier to
guide the user to take a look.
Bug: 168635084
Test: 1. adb shell settings put secure \
accessibility_show_window_magnification_prompt 1
2. use full-screen magnification to see if it works well.
atest WindowMagnificationPromptControllerTest
atest FullScreenMagnificationGestureHandlerTest
Change-Id: I40dfc49307d2dad380f1e662960d35f5cc5e7524
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 41be5c4..2237efc 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -57,6 +57,7 @@
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 @@
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 8eb0853..be6b6b1 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 @@
<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 fba431c..a93f91f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3460,6 +3460,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" />
@@ -4097,4 +4098,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 15bd4dc..ae024ff 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -332,5 +332,9 @@
// 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 857ac6a..be7643e 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.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 @@
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 d50e9d7..efb9d87 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 @@
@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 @@
ScaleChangedListener listener,
boolean detectTripleTap,
boolean detectShortcutTrigger,
+ @NonNull WindowMagnificationPromptController promptController,
int displayId) {
super(listener);
if (DEBUG_ALL) {
@@ -186,6 +188,7 @@
+ ", detectShortcutTrigger = " + detectShortcutTrigger + ")");
}
mFullScreenMagnificationController = fullScreenMagnificationController;
+ mPromptController = promptController;
mDisplayId = displayId;
mDelegatingState = new DelegatingState();
@@ -195,7 +198,6 @@
mDetectTripleTap = detectTripleTap;
mDetectShortcutTrigger = detectShortcutTrigger;
-
if (mDetectShortcutTrigger) {
mScreenStateReceiver = new ScreenStateReceiver(context, this);
mScreenStateReceiver.register();
@@ -264,6 +266,7 @@
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 @@
if (wasMagnifying) {
clearAndTransitionToStateDetecting();
} else {
+ mPromptController.showNotificationIfNeeded();
mDetectingState.toggleShortcutTriggered();
}
}
@@ -950,6 +954,7 @@
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 0000000..7212207
--- /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 1cdd873..e43a002 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.mockito.Mock;
import org.mockito.MockitoAnnotations;
-
import java.util.ArrayList;
import java.util.List;
import java.util.function.IntConsumer;
@@ -129,6 +128,8 @@
ScaleChangedListener mMockScaleChangedListener;
@Mock
MagnificationRequestObserver mMagnificationRequestObserver;
+ @Mock
+ WindowMagnificationPromptController mWindowMagnificationPromptController;
private OffsettableClock mClock;
private FullScreenMagnificationGestureHandler mMgh;
@@ -170,7 +171,9 @@
@After
public void tearDown() {
+ mMgh.onDestroy();
mFullScreenMagnificationController.unregister(DISPLAY_0);
+ verify(mWindowMagnificationPromptController).onDestroy();
}
@NonNull
@@ -178,7 +181,8 @@
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 @@
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 0000000..5fd28f57
--- /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;
+ }
+ }
+}