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