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