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 17 package com.android.permissioncontroller.permission.ui; 18 19 import android.accessibilityservice.AccessibilityServiceInfo; 20 import android.app.AlertDialog; 21 import android.app.AppOpsManager; 22 import android.content.ComponentName; 23 import android.content.Intent; 24 import android.content.pm.ApplicationInfo; 25 import android.content.pm.ResolveInfo; 26 import android.content.pm.ServiceInfo; 27 import android.os.Bundle; 28 import android.provider.Settings; 29 import android.text.TextUtils; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.view.accessibility.AccessibilityManager; 34 import android.widget.ImageView; 35 import android.widget.TextView; 36 37 import androidx.annotation.NonNull; 38 import androidx.core.text.BidiFormatter; 39 40 import com.android.permissioncontroller.R; 41 import com.android.permissioncontroller.permission.utils.Utils; 42 43 import java.util.List; 44 45 /** 46 * A dialog listing the currently enabled accessibility services and their last access times. 47 */ 48 public final class ReviewAccessibilityServicesActivity extends SettingsActivity { 49 50 @Override onCreate(Bundle savedInstanceState)51 protected void onCreate(Bundle savedInstanceState) { 52 super.onCreate(savedInstanceState); 53 54 AccessibilityManager accessibilityManager = getSystemService( 55 AccessibilityManager.class); 56 List<AccessibilityServiceInfo> services = accessibilityManager 57 .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK); 58 59 new AlertDialog.Builder(this) 60 .setView(createDialogView(services)) 61 .setPositiveButton(R.string.ok, null) 62 .setNeutralButton(R.string.settings, (dialog, which) -> { 63 if (services.size() == 1) { 64 startAccessibilityScreen(services.get(0).getResolveInfo().serviceInfo); 65 } else { 66 startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)); 67 } 68 }) 69 .setOnDismissListener((dialog) -> finishAfterTransition()) 70 .show(); 71 } 72 createDialogView(List<AccessibilityServiceInfo> services)73 private @NonNull View createDialogView(List<AccessibilityServiceInfo> services) { 74 AppOpsManager appOpsManager = getSystemService(AppOpsManager.class); 75 76 LayoutInflater layoutInflater = LayoutInflater.from(this); 77 View view = layoutInflater.inflate(R.layout.accessibility_service_dialog, null); 78 79 int numServices = services.size(); 80 for (int i = 0; i < numServices; i++) { 81 ResolveInfo resolveInfo = services.get(i).getResolveInfo(); 82 ServiceInfo serviceInfo = resolveInfo.serviceInfo; 83 ApplicationInfo appInfo = serviceInfo.applicationInfo; 84 CharSequence label = getLabel(resolveInfo); 85 long lastAccessTime = getLastAccessTime(appInfo, appOpsManager); 86 87 if (numServices == 1) { 88 // If there is only one enabled service, the dialog has its icon as a header. 89 90 ((TextView) view.requireViewById(R.id.title)).setText( 91 getString(R.string.accessibility_service_dialog_title_single, label)); 92 ((TextView) view.requireViewById(R.id.bottom_text)).setText( 93 getString(R.string.accessibility_service_dialog_bottom_text_single, label)); 94 95 ImageView headerIcon = view.requireViewById(R.id.header_icon); 96 headerIcon.setImageDrawable(Utils.getBadgedIcon(this, appInfo)); 97 headerIcon.setVisibility(View.VISIBLE); 98 99 if (lastAccessTime != 0) { 100 TextView middleText = view.requireViewById(R.id.middle_text); 101 middleText.setText(getString(R.string.app_permission_most_recent_summary, 102 Utils.getAbsoluteTimeString(this, lastAccessTime))); 103 middleText.setVisibility(View.VISIBLE); 104 } 105 } else { 106 // Add an entry for each enabled service. 107 108 ((TextView) view.requireViewById(R.id.title)).setText( 109 getString(R.string.accessibility_service_dialog_title_multiple, 110 services.size())); 111 ((TextView) view.requireViewById(R.id.bottom_text)).setText( 112 getString(R.string.accessibility_service_dialog_bottom_text_multiple)); 113 114 ViewGroup servicesListView = view.requireViewById(R.id.items_container); 115 View itemView = layoutInflater.inflate(R.layout.accessibility_service_dialog_item, 116 servicesListView, false); 117 118 ((TextView) itemView.requireViewById(R.id.title)).setText(label); 119 ((ImageView) itemView.requireViewById(R.id.icon)).setImageDrawable( 120 Utils.getBadgedIcon(this, appInfo)); 121 122 if (lastAccessTime == 0) { 123 itemView.requireViewById(R.id.summary).setVisibility(View.GONE); 124 } else { 125 ((TextView) itemView.requireViewById(R.id.summary)).setText( 126 getString(R.string.app_permission_most_recent_summary, 127 Utils.getAbsoluteTimeString(this, lastAccessTime))); 128 } 129 130 itemView.setOnClickListener((v) -> startAccessibilityScreen(serviceInfo)); 131 132 servicesListView.addView(itemView); 133 } 134 } 135 136 return view; 137 } 138 startAccessibilityScreen(ServiceInfo serviceInfo)139 private void startAccessibilityScreen(ServiceInfo serviceInfo) { 140 Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS); 141 intent.putExtra(Intent.EXTRA_COMPONENT_NAME, 142 new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToString()); 143 startActivity(intent); 144 } 145 getLabel(@onNull ResolveInfo resolveInfo)146 private @NonNull CharSequence getLabel(@NonNull ResolveInfo resolveInfo) { 147 return BidiFormatter.getInstance().unicodeWrap( 148 TextUtils.makeSafeForPresentation( 149 resolveInfo.loadLabel(getPackageManager()).toString(), 0, 0, 150 TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE)); 151 } 152 getLastAccessTime(@onNull ApplicationInfo appInfo, @NonNull AppOpsManager appOpsManager)153 private static long getLastAccessTime(@NonNull ApplicationInfo appInfo, 154 @NonNull AppOpsManager appOpsManager) { 155 List<AppOpsManager.PackageOps> ops = appOpsManager.getOpsForPackage(appInfo.uid, 156 appInfo.packageName, AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY); 157 long lastAccessTime = 0; 158 int numPkgOps = ops.size(); 159 for (int pkgOpNum = 0; pkgOpNum < numPkgOps; pkgOpNum++) { 160 AppOpsManager.PackageOps pkgOp = ops.get(pkgOpNum); 161 int numOps = pkgOp.getOps().size(); 162 for (int opNum = 0; opNum < numOps; opNum++) { 163 AppOpsManager.OpEntry op = pkgOp.getOps().get(opNum); 164 lastAccessTime = Math.max(lastAccessTime, 165 op.getLastAccessTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED)); 166 } 167 } 168 return lastAccessTime; 169 } 170 } 171