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 package com.android.internal.accessibility.dialog; 17 18 import static com.android.internal.accessibility.common.ShortcutConstants.ShortcutMenuMode; 19 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType; 20 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE; 21 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE; 22 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getInstalledTargets; 23 import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets; 24 import static com.android.internal.accessibility.util.AccessibilityUtils.isUserSetupCompleted; 25 26 import android.annotation.Nullable; 27 import android.app.Activity; 28 import android.app.AlertDialog; 29 import android.app.Dialog; 30 import android.app.KeyguardManager; 31 import android.content.Context; 32 import android.content.DialogInterface; 33 import android.content.res.TypedArray; 34 import android.os.Bundle; 35 import android.view.View; 36 import android.view.Window; 37 import android.view.WindowManager; 38 import android.view.accessibility.AccessibilityManager; 39 import android.widget.AdapterView; 40 41 import com.android.internal.R; 42 import com.android.internal.annotations.VisibleForTesting; 43 44 import java.util.ArrayList; 45 import java.util.List; 46 47 /** 48 * Activity used to display various targets related to accessibility service, accessibility 49 * activity or allowlisting feature for volume key shortcut. 50 */ 51 public class AccessibilityShortcutChooserActivity extends Activity { 52 @UserShortcutType 53 private final int mShortcutType = HARDWARE; 54 private static final String KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE = 55 "accessibility_shortcut_menu_mode"; 56 private final List<AccessibilityTarget> mTargets = new ArrayList<>(); 57 private AlertDialog mMenuDialog; 58 private Dialog mPermissionDialog; 59 private ShortcutTargetAdapter mTargetAdapter; 60 61 @Override onCreate(@ullable Bundle savedInstanceState)62 protected void onCreate(@Nullable Bundle savedInstanceState) { 63 super.onCreate(savedInstanceState); 64 65 final TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme); 66 if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, /* defValue= */ false)) { 67 requestWindowFeature(Window.FEATURE_NO_TITLE); 68 } 69 70 mTargets.addAll(getTargets(this, mShortcutType)); 71 mTargetAdapter = new ShortcutTargetAdapter(mTargets); 72 mMenuDialog = createMenuDialog(); 73 mMenuDialog.setOnShowListener(dialog -> updateDialogListeners()); 74 mMenuDialog.show(); 75 76 if (savedInstanceState != null) { 77 final int restoreShortcutMenuMode = 78 savedInstanceState.getInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, 79 ShortcutMenuMode.LAUNCH); 80 if (restoreShortcutMenuMode == ShortcutMenuMode.EDIT) { 81 onEditButtonClicked(); 82 } 83 } 84 } 85 86 @Override onDestroy()87 protected void onDestroy() { 88 mMenuDialog.setOnDismissListener(null); 89 mMenuDialog.dismiss(); 90 super.onDestroy(); 91 } 92 93 @Override onSaveInstanceState(Bundle outState)94 protected void onSaveInstanceState(Bundle outState) { 95 super.onSaveInstanceState(outState); 96 outState.putInt(KEY_ACCESSIBILITY_SHORTCUT_MENU_MODE, mTargetAdapter.getShortcutMenuMode()); 97 } 98 onTargetSelected(AdapterView<?> parent, View view, int position, long id)99 private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) { 100 final AccessibilityTarget target = mTargets.get(position); 101 if (target instanceof AccessibilityServiceTarget 102 || target instanceof AccessibilityActivityTarget) { 103 if (sendRestrictedDialogIntentIfNeeded(target)) { 104 return; 105 } 106 } 107 108 target.onSelected(); 109 mMenuDialog.dismiss(); 110 } 111 onTargetChecked(AdapterView<?> parent, View view, int position, long id)112 private void onTargetChecked(AdapterView<?> parent, View view, int position, long id) { 113 final AccessibilityTarget target = mTargets.get(position); 114 115 if (target instanceof AccessibilityServiceTarget serviceTarget) { 116 if (sendRestrictedDialogIntentIfNeeded(target)) { 117 return; 118 } 119 final AccessibilityManager am = getSystemService(AccessibilityManager.class); 120 if (am.isAccessibilityServiceWarningRequired( 121 serviceTarget.getAccessibilityServiceInfo())) { 122 showPermissionDialogIfNeeded(this, (AccessibilityServiceTarget) target, 123 position, mTargetAdapter); 124 return; 125 } 126 } 127 if (target instanceof AccessibilityActivityTarget activityTarget) { 128 if (!activityTarget.isShortcutEnabled() 129 && sendRestrictedDialogIntentIfNeeded(activityTarget)) { 130 return; 131 } 132 } 133 134 target.onCheckedChanged(!target.isShortcutEnabled()); 135 mTargetAdapter.notifyDataSetChanged(); 136 } 137 138 /** 139 * Sends restricted dialog intent if the accessibility target is disallowed. 140 * 141 * @return true if sends restricted dialog intent, otherwise false. 142 */ sendRestrictedDialogIntentIfNeeded(AccessibilityTarget target)143 private boolean sendRestrictedDialogIntentIfNeeded(AccessibilityTarget target) { 144 if (AccessibilityTargetHelper.isAccessibilityTargetAllowed(this, 145 target.getComponentName().getPackageName(), target.getUid())) { 146 return false; 147 } 148 149 AccessibilityTargetHelper.sendRestrictedDialogIntent(this, 150 target.getComponentName().getPackageName(), target.getUid()); 151 return true; 152 } 153 showPermissionDialogIfNeeded(Context context, AccessibilityServiceTarget serviceTarget, int position, ShortcutTargetAdapter targetAdapter)154 private void showPermissionDialogIfNeeded(Context context, 155 AccessibilityServiceTarget serviceTarget, int position, 156 ShortcutTargetAdapter targetAdapter) { 157 if (mPermissionDialog != null) { 158 return; 159 } 160 161 mPermissionDialog = AccessibilityServiceWarning 162 .createAccessibilityServiceWarningDialog(context, 163 serviceTarget.getAccessibilityServiceInfo(), 164 v -> { 165 serviceTarget.onCheckedChanged(true); 166 targetAdapter.notifyDataSetChanged(); 167 mPermissionDialog.dismiss(); 168 }, v -> { 169 serviceTarget.onCheckedChanged(false); 170 mPermissionDialog.dismiss(); 171 }, 172 v -> { 173 mTargets.remove(position); 174 context.getPackageManager().getPackageInstaller().uninstall( 175 serviceTarget.getComponentName().getPackageName(), null); 176 targetAdapter.notifyDataSetChanged(); 177 mPermissionDialog.dismiss(); 178 }); 179 mPermissionDialog.setOnDismissListener(dialog -> mPermissionDialog = null); 180 mPermissionDialog.show(); 181 } 182 onDoneButtonClicked()183 private void onDoneButtonClicked() { 184 mTargets.clear(); 185 mTargets.addAll(getTargets(this, mShortcutType)); 186 if (mTargets.isEmpty()) { 187 mMenuDialog.dismiss(); 188 return; 189 } 190 191 mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH); 192 mTargetAdapter.notifyDataSetChanged(); 193 194 mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText( 195 getString(R.string.edit_accessibility_shortcut_menu_button)); 196 197 updateDialogListeners(); 198 } 199 onEditButtonClicked()200 private void onEditButtonClicked() { 201 mTargets.clear(); 202 mTargets.addAll(getInstalledTargets(this, mShortcutType)); 203 mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT); 204 mTargetAdapter.notifyDataSetChanged(); 205 206 mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText( 207 getString(R.string.done_accessibility_shortcut_menu_button)); 208 209 updateDialogListeners(); 210 } 211 updateDialogListeners()212 private void updateDialogListeners() { 213 final boolean isEditMenuMode = 214 mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT; 215 final int selectDialogTitleId = R.string.accessibility_select_shortcut_menu_title; 216 final int editDialogTitleId = 217 mShortcutType == SOFTWARE 218 ? R.string.accessibility_edit_shortcut_menu_button_title 219 : R.string.accessibility_edit_shortcut_menu_volume_title; 220 221 mMenuDialog.setTitle(getString(isEditMenuMode ? editDialogTitleId : selectDialogTitleId)); 222 mMenuDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener( 223 isEditMenuMode ? view -> onDoneButtonClicked() : view -> onEditButtonClicked()); 224 mMenuDialog.getListView().setOnItemClickListener( 225 isEditMenuMode ? this::onTargetChecked : this::onTargetSelected); 226 } 227 228 @VisibleForTesting getMenuDialog()229 public AlertDialog getMenuDialog() { 230 return mMenuDialog; 231 } 232 233 @VisibleForTesting getPermissionDialog()234 public Dialog getPermissionDialog() { 235 return mPermissionDialog; 236 } 237 createMenuDialog()238 private AlertDialog createMenuDialog() { 239 final String dialogTitle = 240 getString(R.string.accessibility_select_shortcut_menu_title); 241 242 final AlertDialog.Builder builder = new AlertDialog.Builder(this) 243 .setTitle(dialogTitle) 244 .setAdapter(mTargetAdapter, /* listener= */ null) 245 .setOnDismissListener(dialog -> finish()); 246 247 boolean allowEditing = isUserSetupCompleted(this); 248 boolean showWhenLocked = false; 249 final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class); 250 if (keyguardManager != null && keyguardManager.isKeyguardLocked()) { 251 allowEditing = false; 252 showWhenLocked = true; 253 } 254 if (allowEditing) { 255 final String positiveButtonText = 256 getString(R.string.edit_accessibility_shortcut_menu_button); 257 builder.setPositiveButton(positiveButtonText, /* listener= */ null); 258 } 259 260 final AlertDialog dialog = builder.create(); 261 if (showWhenLocked) { 262 dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); 263 } 264 return dialog; 265 } 266 } 267