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 17 package com.android.settings.accessibility; 18 19 import static com.android.settings.accessibility.AccessibilityStatsLogUtils.logAccessibilityServiceEnabled; 20 21 import android.accessibilityservice.AccessibilityShortcutInfo; 22 import android.app.ActivityOptions; 23 import android.content.ActivityNotFoundException; 24 import android.content.ComponentName; 25 import android.content.ContentResolver; 26 import android.content.Intent; 27 import android.content.pm.ActivityInfo; 28 import android.net.Uri; 29 import android.os.Bundle; 30 import android.os.UserHandle; 31 import android.text.TextUtils; 32 import android.util.Log; 33 import android.view.LayoutInflater; 34 import android.view.Menu; 35 import android.view.MenuInflater; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.view.accessibility.AccessibilityManager; 39 40 import androidx.annotation.Nullable; 41 import androidx.preference.Preference; 42 43 import com.android.settings.R; 44 import com.android.settings.overlay.FeatureFactory; 45 46 import java.util.ArrayList; 47 import java.util.List; 48 49 /** Fragment for providing open activity button. */ 50 public class LaunchAccessibilityActivityPreferenceFragment extends ToggleFeaturePreferenceFragment { 51 private static final String TAG = "LaunchA11yActivity"; 52 private static final String EMPTY_STRING = ""; 53 protected static final String KEY_LAUNCH_PREFERENCE = "launch_preference"; 54 55 @Override getMetricsCategory()56 public int getMetricsCategory() { 57 // Retrieve from getArguments() directly because this function will be executed from 58 // onAttach(), but variable mComponentName only available after onProcessArguments() 59 // which comes from onCreateView(). 60 final ComponentName componentName = getArguments().getParcelable( 61 AccessibilitySettings.EXTRA_COMPONENT_NAME); 62 63 return FeatureFactory.getFactory(getActivity().getApplicationContext()) 64 .getAccessibilityMetricsFeatureProvider() 65 .getDownloadedFeatureMetricsCategory(componentName); 66 } 67 68 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)69 public View onCreateView(LayoutInflater inflater, ViewGroup container, 70 Bundle savedInstanceState) { 71 final View view = super.onCreateView(inflater, container, savedInstanceState); 72 73 // Init new preference to replace the switch preference instead. 74 initLaunchPreference(); 75 removePreference(KEY_USE_SERVICE_PREFERENCE); 76 return view; 77 } 78 79 @Override onPreferenceToggled(String preferenceKey, boolean enabled)80 protected void onPreferenceToggled(String preferenceKey, boolean enabled) { 81 // Do nothing. 82 } 83 84 @Override onProcessArguments(Bundle arguments)85 protected void onProcessArguments(Bundle arguments) { 86 super.onProcessArguments(arguments); 87 mComponentName = arguments.getParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME); 88 final ActivityInfo info = getAccessibilityShortcutInfo().getActivityInfo(); 89 mPackageName = info.loadLabel(getPackageManager()).toString(); 90 91 // Settings animated image. 92 final int animatedImageRes = arguments.getInt( 93 AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES); 94 if (animatedImageRes > 0) { 95 mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) 96 .authority(mComponentName.getPackageName()) 97 .appendPath(String.valueOf(animatedImageRes)) 98 .build(); 99 } 100 101 // Settings html description. 102 mHtmlDescription = arguments.getCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION); 103 104 // Settings title and intent. 105 final String settingsTitle = arguments.getString( 106 AccessibilitySettings.EXTRA_SETTINGS_TITLE); 107 mSettingsIntent = TextUtils.isEmpty(settingsTitle) ? null : getSettingsIntent(arguments); 108 mSettingsTitle = (mSettingsIntent == null) ? null : settingsTitle; 109 } 110 111 @Override getUserShortcutTypes()112 int getUserShortcutTypes() { 113 return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(), 114 mComponentName); 115 } 116 117 @Override onCreateOptionsMenu(Menu menu, MenuInflater inflater)118 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 119 // Do not call super. We don't want to see the "Help & feedback" option on this page so as 120 // not to confuse users who think they might be able to send feedback about a specific 121 // accessibility service from this page. 122 } 123 124 // IMPORTANT: Refresh the info since there are dynamically changing capabilities. getAccessibilityShortcutInfo()125 private AccessibilityShortcutInfo getAccessibilityShortcutInfo() { 126 final List<AccessibilityShortcutInfo> infos = AccessibilityManager.getInstance( 127 getPrefContext()).getInstalledAccessibilityShortcutListAsUser(getPrefContext(), 128 UserHandle.myUserId()); 129 130 for (int i = 0, count = infos.size(); i < count; i++) { 131 AccessibilityShortcutInfo shortcutInfo = infos.get(i); 132 ActivityInfo activityInfo = shortcutInfo.getActivityInfo(); 133 if (mComponentName.getPackageName().equals(activityInfo.packageName) 134 && mComponentName.getClassName().equals(activityInfo.name)) { 135 return shortcutInfo; 136 } 137 } 138 return null; 139 } 140 141 /** Customizes the order by preference key. */ getPreferenceOrderList()142 protected List<String> getPreferenceOrderList() { 143 final List<String> lists = new ArrayList<>(); 144 lists.add(KEY_ANIMATED_IMAGE); 145 lists.add(KEY_LAUNCH_PREFERENCE); 146 lists.add(KEY_GENERAL_CATEGORY); 147 lists.add(KEY_HTML_DESCRIPTION_PREFERENCE); 148 return lists; 149 } 150 initLaunchPreference()151 private void initLaunchPreference() { 152 final Preference launchPreference = new Preference(getPrefContext()); 153 launchPreference.setLayoutResource(R.layout.accessibility_launch_activity_preference); 154 launchPreference.setKey(KEY_LAUNCH_PREFERENCE); 155 156 final AccessibilityShortcutInfo info = getAccessibilityShortcutInfo(); 157 final String switchBarText = (info == null) ? EMPTY_STRING : getString( 158 R.string.accessibility_service_primary_open_title, 159 info.getActivityInfo().loadLabel(getPackageManager())); 160 launchPreference.setTitle(switchBarText); 161 162 launchPreference.setOnPreferenceClickListener(preference -> { 163 logAccessibilityServiceEnabled(mComponentName, /* enabled= */ true); 164 launchShortcutTargetActivity(getPrefContext().getDisplayId(), mComponentName); 165 return true; 166 }); 167 getPreferenceScreen().addPreference(launchPreference); 168 } 169 launchShortcutTargetActivity(int displayId, ComponentName name)170 private void launchShortcutTargetActivity(int displayId, ComponentName name) { 171 final Intent intent = new Intent(); 172 final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(); 173 174 intent.setComponent(name); 175 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 176 try { 177 final int userId = UserHandle.myUserId(); 178 getPrefContext().startActivityAsUser(intent, bundle, UserHandle.of(userId)); 179 } catch (ActivityNotFoundException ignore) { 180 // ignore the exception 181 Log.w(TAG, "Target activity not found."); 182 } 183 } 184 185 @Nullable getSettingsIntent(Bundle arguments)186 private Intent getSettingsIntent(Bundle arguments) { 187 final String settingsComponentName = arguments.getString( 188 AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME); 189 if (TextUtils.isEmpty(settingsComponentName)) { 190 return null; 191 } 192 193 final Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent( 194 ComponentName.unflattenFromString(settingsComponentName)); 195 if (getPackageManager().queryIntentActivities(settingsIntent, /* flags= */ 0).isEmpty()) { 196 return null; 197 } 198 199 return settingsIntent; 200 } 201 } 202