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