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