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