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