• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.car.developeroptions.accessibility;
17 
18 import static android.content.DialogInterface.BUTTON_POSITIVE;
19 
20 import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
21 import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
22 
23 import android.accessibilityservice.AccessibilityServiceInfo;
24 import android.app.Dialog;
25 import android.app.settings.SettingsEnums;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.PackageManager;
31 import android.content.pm.ResolveInfo;
32 import android.graphics.drawable.Drawable;
33 import android.os.Binder;
34 import android.os.Bundle;
35 import android.os.IBinder;
36 import android.os.UserHandle;
37 import android.provider.Settings;
38 import android.text.TextUtils;
39 import android.view.accessibility.AccessibilityManager;
40 
41 import androidx.fragment.app.Fragment;
42 import androidx.fragment.app.FragmentActivity;
43 
44 import com.android.internal.accessibility.AccessibilityShortcutController;
45 import com.android.internal.accessibility.AccessibilityShortcutController.ToggleableFrameworkFeatureInfo;
46 import com.android.car.developeroptions.R;
47 import com.android.car.developeroptions.core.instrumentation.InstrumentedDialogFragment;
48 import com.android.car.developeroptions.widget.RadioButtonPickerFragment;
49 import com.android.car.developeroptions.widget.RadioButtonPreference;
50 import com.android.settingslib.accessibility.AccessibilityUtils;
51 import com.android.settingslib.widget.CandidateInfo;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 import java.util.Map;
56 
57 /**
58  * Fragment for picking accessibility shortcut service
59  */
60 public class ShortcutServicePickerFragment extends RadioButtonPickerFragment {
61 
62     @Override
getMetricsCategory()63     public int getMetricsCategory() {
64         return SettingsEnums.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE;
65     }
66 
67     @Override
getPreferenceScreenResId()68     protected int getPreferenceScreenResId() {
69         return R.xml.accessibility_shortcut_service_settings;
70     }
71 
72     @Override
getCandidates()73     protected List<? extends CandidateInfo> getCandidates() {
74         final Context context = getContext();
75         final AccessibilityManager accessibilityManager = context
76                 .getSystemService(AccessibilityManager.class);
77         final List<AccessibilityServiceInfo> installedServices =
78                 accessibilityManager.getInstalledAccessibilityServiceList();
79         final int numInstalledServices = installedServices.size();
80 
81         final List<CandidateInfo> candidates = new ArrayList<>(numInstalledServices);
82         Map<ComponentName, ToggleableFrameworkFeatureInfo> frameworkFeatureInfoMap =
83                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
84         for (ComponentName componentName : frameworkFeatureInfoMap.keySet()) {
85             final int iconId;
86             if (componentName.equals(COLOR_INVERSION_COMPONENT_NAME)) {
87                 iconId = R.drawable.ic_color_inversion;
88             } else if (componentName.equals(DALTONIZER_COMPONENT_NAME)) {
89                 iconId = R.drawable.ic_daltonizer;
90             } else {
91                 iconId = R.drawable.empty_icon;
92             }
93             candidates.add(new FrameworkCandidateInfo(frameworkFeatureInfoMap.get(componentName),
94                     iconId, componentName.flattenToString()));
95         }
96         for (int i = 0; i < numInstalledServices; i++) {
97             candidates.add(new ServiceCandidateInfo(installedServices.get(i)));
98         }
99 
100         return candidates;
101     }
102 
103     @Override
getDefaultKey()104     protected String getDefaultKey() {
105         String shortcutServiceString = AccessibilityUtils
106                 .getShortcutTargetServiceComponentNameString(getContext(), UserHandle.myUserId());
107         if (shortcutServiceString != null) {
108             ComponentName shortcutName = ComponentName.unflattenFromString(shortcutServiceString);
109             if (shortcutName != null) {
110                 return shortcutName.flattenToString();
111             }
112         }
113         return null;
114     }
115 
116     @Override
setDefaultKey(String key)117     protected boolean setDefaultKey(String key) {
118         Settings.Secure.putString(getContext().getContentResolver(),
119                 Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, key);
120         return true;
121     }
122 
123     @Override
onRadioButtonClicked(RadioButtonPreference selected)124     public void onRadioButtonClicked(RadioButtonPreference selected) {
125         final String selectedKey = selected.getKey();
126 
127         if (TextUtils.isEmpty(selectedKey)) {
128             super.onRadioButtonClicked(selected);
129         } else {
130             final ComponentName selectedComponent = ComponentName.unflattenFromString(selectedKey);
131             if (AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
132                     .containsKey(selectedComponent)) {
133                 // This is a framework feature. It doesn't need to be confirmed.
134                 onRadioButtonConfirmed(selectedKey);
135             } else {
136                 final FragmentActivity activity = getActivity();
137                 if (activity != null) {
138                     ConfirmationDialogFragment.newInstance(this, selectedKey)
139                             .show(activity.getSupportFragmentManager(),
140                                     ConfirmationDialogFragment.TAG);
141                 }
142             }
143         }
144     }
145 
onServiceConfirmed(String serviceKey)146     private void onServiceConfirmed(String serviceKey) {
147         onRadioButtonConfirmed(serviceKey);
148     }
149 
150     public static class ConfirmationDialogFragment extends InstrumentedDialogFragment
151             implements DialogInterface.OnClickListener {
152         private static final String EXTRA_KEY = "extra_key";
153         private static final String TAG = "ConfirmationDialogFragment";
154         private IBinder mToken;
155 
newInstance(ShortcutServicePickerFragment parent, String key)156         public static ConfirmationDialogFragment newInstance(ShortcutServicePickerFragment parent,
157                 String key) {
158             final ConfirmationDialogFragment fragment = new ConfirmationDialogFragment();
159             final Bundle argument = new Bundle();
160             argument.putString(EXTRA_KEY, key);
161             fragment.setArguments(argument);
162             fragment.setTargetFragment(parent, 0);
163             fragment.mToken = new Binder();
164             return fragment;
165         }
166 
167         @Override
getMetricsCategory()168         public int getMetricsCategory() {
169             return SettingsEnums.ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE;
170         }
171 
172         @Override
onCreateDialog(Bundle savedInstanceState)173         public Dialog onCreateDialog(Bundle savedInstanceState) {
174             final Bundle bundle = getArguments();
175             final String key = bundle.getString(EXTRA_KEY);
176             final ComponentName serviceComponentName = ComponentName.unflattenFromString(key);
177             final AccessibilityManager accessibilityManager = getActivity()
178                     .getSystemService(AccessibilityManager.class);
179             AccessibilityServiceInfo info = accessibilityManager
180                     .getInstalledServiceInfoWithComponentName(serviceComponentName);
181             return AccessibilityServiceWarning.createCapabilitiesDialog(getActivity(), info, this);
182         }
183 
184         @Override
onClick(DialogInterface dialog, int which)185         public void onClick(DialogInterface dialog, int which) {
186             final Fragment fragment = getTargetFragment();
187             if ((which == BUTTON_POSITIVE) && (fragment instanceof ShortcutServicePickerFragment)) {
188                 final Bundle bundle = getArguments();
189                 ((ShortcutServicePickerFragment) fragment).onServiceConfirmed(
190                         bundle.getString(EXTRA_KEY));
191             }
192         }
193     }
194 
195     private class FrameworkCandidateInfo extends CandidateInfo {
196         final ToggleableFrameworkFeatureInfo mToggleableFrameworkFeatureInfo;
197         final int mIconResId;
198         final String mKey;
199 
FrameworkCandidateInfo( ToggleableFrameworkFeatureInfo frameworkFeatureInfo, int iconResId, String key)200         public FrameworkCandidateInfo(
201                 ToggleableFrameworkFeatureInfo frameworkFeatureInfo, int iconResId, String key) {
202             super(true /* enabled */);
203             mToggleableFrameworkFeatureInfo = frameworkFeatureInfo;
204             mIconResId = iconResId;
205             mKey = key;
206         }
207 
208         @Override
loadLabel()209         public CharSequence loadLabel() {
210             return mToggleableFrameworkFeatureInfo.getLabel(getContext());
211         }
212 
213         @Override
loadIcon()214         public Drawable loadIcon() {
215             return getContext().getDrawable(mIconResId);
216         }
217 
218         @Override
getKey()219         public String getKey() {
220             return mKey;
221         }
222     }
223 
224     private class ServiceCandidateInfo extends CandidateInfo {
225         final AccessibilityServiceInfo mServiceInfo;
226 
ServiceCandidateInfo(AccessibilityServiceInfo serviceInfo)227         public ServiceCandidateInfo(AccessibilityServiceInfo serviceInfo) {
228             super(true /* enabled */);
229             mServiceInfo = serviceInfo;
230         }
231 
232         @Override
loadLabel()233         public CharSequence loadLabel() {
234             final PackageManager pmw = getContext().getPackageManager();
235             final CharSequence label =
236                     mServiceInfo.getResolveInfo().serviceInfo.loadLabel(pmw);
237             if (label != null) {
238                 return label;
239             }
240 
241             final ComponentName componentName = mServiceInfo.getComponentName();
242             if (componentName != null) {
243                 try {
244                     final ApplicationInfo appInfo = pmw.getApplicationInfoAsUser(
245                             componentName.getPackageName(), 0, UserHandle.myUserId());
246                     return appInfo.loadLabel(pmw);
247                 } catch (PackageManager.NameNotFoundException e) {
248                     return null;
249                 }
250             }
251             return null;
252         }
253 
254         @Override
loadIcon()255         public Drawable loadIcon() {
256             final ResolveInfo resolveInfo = mServiceInfo.getResolveInfo();
257             return (resolveInfo.getIconResource() == 0)
258                     ? getContext().getDrawable(R.drawable.ic_accessibility_generic)
259                     : resolveInfo.loadIcon(getContext().getPackageManager());
260         }
261 
262         @Override
getKey()263         public String getKey() {
264             return mServiceInfo.getComponentName().flattenToString();
265         }
266     }
267 }
268