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;
+        }
+    }
+}