/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.permissioncontroller.permission.ui; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.AlertDialog; import android.app.AppOpsManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.provider.Settings; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.core.text.BidiFormatter; import com.android.permissioncontroller.R; import com.android.permissioncontroller.permission.utils.Utils; import java.util.List; /** * A dialog listing the currently enabled accessibility services and their last access times. */ public final class ReviewAccessibilityServicesActivity extends SettingsActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); AccessibilityManager accessibilityManager = getSystemService( AccessibilityManager.class); List<AccessibilityServiceInfo> services = accessibilityManager .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK); new AlertDialog.Builder(this) .setView(createDialogView(services)) .setPositiveButton(R.string.ok, null) .setNeutralButton(R.string.settings, (dialog, which) -> { if (services.size() == 1) { startAccessibilityScreen(services.get(0).getResolveInfo().serviceInfo); } else { startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)); } }) .setOnDismissListener((dialog) -> finishAfterTransition()) .show(); } private @NonNull View createDialogView(List<AccessibilityServiceInfo> services) { AppOpsManager appOpsManager = getSystemService(AppOpsManager.class); LayoutInflater layoutInflater = LayoutInflater.from(this); View view = layoutInflater.inflate(R.layout.accessibility_service_dialog, null); int numServices = services.size(); for (int i = 0; i < numServices; i++) { ResolveInfo resolveInfo = services.get(i).getResolveInfo(); ServiceInfo serviceInfo = resolveInfo.serviceInfo; ApplicationInfo appInfo = serviceInfo.applicationInfo; CharSequence label = getLabel(resolveInfo); long lastAccessTime = getLastAccessTime(appInfo, appOpsManager); if (numServices == 1) { // If there is only one enabled service, the dialog has its icon as a header. ((TextView) view.requireViewById(R.id.title)).setText( getString(R.string.accessibility_service_dialog_title_single, label)); ((TextView) view.requireViewById(R.id.bottom_text)).setText( getString(R.string.accessibility_service_dialog_bottom_text_single, label)); ImageView headerIcon = view.requireViewById(R.id.header_icon); headerIcon.setImageDrawable(Utils.getBadgedIcon(this, appInfo)); headerIcon.setVisibility(View.VISIBLE); if (lastAccessTime != 0) { TextView middleText = view.requireViewById(R.id.middle_text); middleText.setText(getString(R.string.app_permission_most_recent_summary, Utils.getAbsoluteTimeString(this, lastAccessTime))); middleText.setVisibility(View.VISIBLE); } } else { // Add an entry for each enabled service. ((TextView) view.requireViewById(R.id.title)).setText( getString(R.string.accessibility_service_dialog_title_multiple, services.size())); ((TextView) view.requireViewById(R.id.bottom_text)).setText( getString(R.string.accessibility_service_dialog_bottom_text_multiple)); ViewGroup servicesListView = view.requireViewById(R.id.items_container); View itemView = layoutInflater.inflate(R.layout.accessibility_service_dialog_item, servicesListView, false); ((TextView) itemView.requireViewById(R.id.title)).setText(label); ((ImageView) itemView.requireViewById(R.id.icon)).setImageDrawable( Utils.getBadgedIcon(this, appInfo)); if (lastAccessTime == 0) { itemView.requireViewById(R.id.summary).setVisibility(View.GONE); } else { ((TextView) itemView.requireViewById(R.id.summary)).setText( getString(R.string.app_permission_most_recent_summary, Utils.getAbsoluteTimeString(this, lastAccessTime))); } itemView.setOnClickListener((v) -> startAccessibilityScreen(serviceInfo)); servicesListView.addView(itemView); } } return view; } private void startAccessibilityScreen(ServiceInfo serviceInfo) { Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS); intent.putExtra(Intent.EXTRA_COMPONENT_NAME, new ComponentName(serviceInfo.packageName, serviceInfo.name).flattenToString()); startActivity(intent); } private @NonNull CharSequence getLabel(@NonNull ResolveInfo resolveInfo) { return BidiFormatter.getInstance().unicodeWrap( TextUtils.makeSafeForPresentation( resolveInfo.loadLabel(getPackageManager()).toString(), 0, 0, TextUtils.SAFE_STRING_FLAG_TRIM | TextUtils.SAFE_STRING_FLAG_FIRST_LINE)); } private static long getLastAccessTime(@NonNull ApplicationInfo appInfo, @NonNull AppOpsManager appOpsManager) { List<AppOpsManager.PackageOps> ops = appOpsManager.getOpsForPackage(appInfo.uid, appInfo.packageName, AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY); long lastAccessTime = 0; int numPkgOps = ops.size(); for (int pkgOpNum = 0; pkgOpNum < numPkgOps; pkgOpNum++) { AppOpsManager.PackageOps pkgOp = ops.get(pkgOpNum); int numOps = pkgOp.getOps().size(); for (int opNum = 0; opNum < numOps; opNum++) { AppOpsManager.OpEntry op = pkgOp.getOps().get(opNum); lastAccessTime = Math.max(lastAccessTime, op.getLastAccessTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED)); } } return lastAccessTime; } }