1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.accessibility.magnification;
18 
19 import static android.provider.Settings.Secure.ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT;
20 
21 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
22 import static com.android.internal.messages.nano.SystemMessageProto.SystemMessage.NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE;
23 
24 import android.Manifest;
25 import android.annotation.MainThread;
26 import android.annotation.NonNull;
27 import android.app.ActivityOptions;
28 import android.app.Notification;
29 import android.app.NotificationManager;
30 import android.app.PendingIntent;
31 import android.app.StatusBarManager;
32 import android.content.BroadcastReceiver;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.IntentFilter;
36 import android.database.ContentObserver;
37 import android.graphics.drawable.Icon;
38 import android.net.Uri;
39 import android.os.Bundle;
40 import android.os.UserHandle;
41 import android.provider.Settings;
42 import android.text.TextUtils;
43 
44 import com.android.internal.R;
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.notification.SystemNotificationChannels;
47 
48 /**
49  * A class to show notification to prompt the user that this feature is available.
50  */
51 public class WindowMagnificationPromptController {
52 
53     private static final Uri MAGNIFICATION_WINDOW_MODE_PROMPT_URI = Settings.Secure.getUriFor(
54             ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT);
55     @VisibleForTesting
56     static final String ACTION_DISMISS =
57             "com.android.server.accessibility.magnification.action.DISMISS";
58     @VisibleForTesting
59     static final String ACTION_TURN_ON_IN_SETTINGS =
60             "com.android.server.accessibility.magnification.action.TURN_ON_IN_SETTINGS";
61     private final Context mContext;
62     private final NotificationManager mNotificationManager;
63     private final ContentObserver mContentObserver;
64     private final int mUserId;
65     @VisibleForTesting
66     BroadcastReceiver mNotificationActionReceiver;
67 
68     private boolean mNeedToShowNotification;
69 
70     @MainThread
WindowMagnificationPromptController(@onNull Context context, int userId)71     public WindowMagnificationPromptController(@NonNull Context context, int userId) {
72         mContext = context;
73         mNotificationManager = context.getSystemService(NotificationManager.class);
74         mUserId = userId;
75         mContentObserver = new ContentObserver(null) {
76             @Override
77             public void onChange(boolean selfChange) {
78                 super.onChange(selfChange);
79                 onPromptSettingsValueChanged();
80             }
81         };
82         context.getContentResolver().registerContentObserver(MAGNIFICATION_WINDOW_MODE_PROMPT_URI,
83                 false, mContentObserver, mUserId);
84         mNeedToShowNotification = isWindowMagnificationPromptEnabled();
85     }
86 
87     @VisibleForTesting
onPromptSettingsValueChanged()88     protected void onPromptSettingsValueChanged() {
89         final boolean needToShowNotification = isWindowMagnificationPromptEnabled();
90         if (mNeedToShowNotification == needToShowNotification) {
91             return;
92         }
93         mNeedToShowNotification = needToShowNotification;
94         if (!mNeedToShowNotification) {
95             unregisterReceiverIfNeeded();
96             mNotificationManager.cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
97         }
98     }
99 
100     /**
101      * Shows the prompt notification that could bring users to magnification settings if necessary.
102      */
103     @MainThread
showNotificationIfNeeded()104     void showNotificationIfNeeded() {
105         if (!mNeedToShowNotification) return;
106 
107         final Notification.Builder notificationBuilder = new Notification.Builder(mContext,
108                 SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION);
109         final String message = mContext.getString(R.string.window_magnification_prompt_content);
110 
111         notificationBuilder.setSmallIcon(R.drawable.ic_accessibility_24dp)
112                 .setContentTitle(mContext.getString(R.string.window_magnification_prompt_title))
113                 .setContentText(message)
114                 .setLargeIcon(Icon.createWithResource(mContext,
115                         R.drawable.ic_accessibility_magnification))
116                 .setTicker(mContext.getString(R.string.window_magnification_prompt_title))
117                 .setOnlyAlertOnce(true)
118                 .setStyle(new Notification.BigTextStyle().bigText(message))
119                 .setDeleteIntent(createPendingIntent(ACTION_DISMISS))
120                 .setContentIntent(createPendingIntent(ACTION_TURN_ON_IN_SETTINGS))
121                 .setActions(buildTurnOnAction());
122         mNotificationManager.notify(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE,
123                 notificationBuilder.build());
124         registerReceiverIfNeeded();
125     }
126 
127     /**
128      * Called when this object is not used anymore to release resources if necessary.
129      */
130     @VisibleForTesting
131     @MainThread
onDestroy()132     public void onDestroy() {
133         dismissNotification();
134         mContext.getContentResolver().unregisterContentObserver(mContentObserver);
135     }
136 
isWindowMagnificationPromptEnabled()137     private boolean isWindowMagnificationPromptEnabled() {
138         return Settings.Secure.getIntForUser(mContext.getContentResolver(),
139                 ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, 0, mUserId) == 1;
140     }
141 
buildTurnOnAction()142     private Notification.Action buildTurnOnAction() {
143         return new Notification.Action.Builder(null,
144                 mContext.getString(R.string.turn_on_magnification_settings_action),
145                 createPendingIntent(ACTION_TURN_ON_IN_SETTINGS)).build();
146     }
147 
createPendingIntent(String action)148     private PendingIntent createPendingIntent(String action) {
149         final Intent intent = new Intent(action);
150         intent.setPackage(mContext.getPackageName());
151         return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
152     }
153 
registerReceiverIfNeeded()154     private void registerReceiverIfNeeded() {
155         if (mNotificationActionReceiver != null) {
156             return;
157         }
158         mNotificationActionReceiver = new NotificationActionReceiver();
159         final IntentFilter intentFilter = new IntentFilter();
160         intentFilter.addAction(ACTION_DISMISS);
161         intentFilter.addAction(ACTION_TURN_ON_IN_SETTINGS);
162         mContext.registerReceiver(mNotificationActionReceiver, intentFilter,
163                 Manifest.permission.MANAGE_ACCESSIBILITY, null, Context.RECEIVER_EXPORTED);
164     }
165 
launchMagnificationSettings()166     private void launchMagnificationSettings() {
167         final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
168         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
169         intent.putExtra(Intent.EXTRA_COMPONENT_NAME,
170                 MAGNIFICATION_COMPONENT_NAME.flattenToShortString());
171         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
172         final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(
173                 mContext.getDisplayId()).toBundle();
174         mContext.startActivityAsUser(intent, bundle, UserHandle.of(mUserId));
175         mContext.getSystemService(StatusBarManager.class).collapsePanels();
176     }
177 
dismissNotification()178     private void dismissNotification() {
179         unregisterReceiverIfNeeded();
180         mNotificationManager.cancel(NOTE_A11Y_WINDOW_MAGNIFICATION_FEATURE);
181     }
182 
unregisterReceiverIfNeeded()183     private void unregisterReceiverIfNeeded() {
184         if (mNotificationActionReceiver == null) {
185             return;
186         }
187         mContext.unregisterReceiver(mNotificationActionReceiver);
188         mNotificationActionReceiver = null;
189     }
190 
191     private class NotificationActionReceiver extends BroadcastReceiver {
192         @Override
onReceive(Context context, Intent intent)193         public void onReceive(Context context, Intent intent) {
194             final String action = intent.getAction();
195             if (TextUtils.isEmpty(action)) return;
196 
197             mNeedToShowNotification = false;
198             Settings.Secure.putIntForUser(mContext.getContentResolver(),
199                     ACCESSIBILITY_SHOW_WINDOW_MAGNIFICATION_PROMPT, 0, mUserId);
200 
201             if (ACTION_TURN_ON_IN_SETTINGS.equals(action)) {
202                 launchMagnificationSettings();
203                 dismissNotification();
204             } else if (ACTION_DISMISS.equals(action)) {
205                 dismissNotification();
206             }
207         }
208     }
209 }
210