1 /*
2  * Copyright (C) 2016 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 android.accessibilityservice.AccessibilityServiceInfo;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.os.storage.StorageManager;
26 import android.text.BidiFormatter;
27 import android.view.LayoutInflater;
28 import android.view.MotionEvent;
29 import android.view.View;
30 import android.widget.ImageView;
31 import android.widget.LinearLayout;
32 import android.widget.TextView;
33 import android.widget.Toast;
34 
35 import com.android.settings.R;
36 
37 import java.util.List;
38 import java.util.Locale;
39 
40 /**
41  * Utility class for creating the dialog that asks users for explicit permission to grant
42  * all of the requested capabilities to an accessibility service before the service is enabled
43  */
44 public class AccessibilityServiceWarning {
createCapabilitiesDialog(Activity parentActivity, AccessibilityServiceInfo info, DialogInterface.OnClickListener listener)45     public static Dialog createCapabilitiesDialog(Activity parentActivity,
46             AccessibilityServiceInfo info, DialogInterface.OnClickListener listener) {
47         final AlertDialog ad = new AlertDialog.Builder(parentActivity)
48                 .setTitle(parentActivity.getString(R.string.enable_service_title,
49                         getServiceName(parentActivity, info)))
50                 .setView(createEnableDialogContentView(parentActivity, info))
51                 .setPositiveButton(android.R.string.ok, listener)
52                 .setNegativeButton(android.R.string.cancel, listener)
53                 .create();
54 
55         final View.OnTouchListener filterTouchListener = (View v, MotionEvent event) -> {
56             // Filter obscured touches by consuming them.
57             if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)
58                 || ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) {
59                 if (event.getAction() == MotionEvent.ACTION_UP) {
60                     Toast.makeText(v.getContext(), R.string.touch_filtered_warning,
61                             Toast.LENGTH_SHORT).show();
62                 }
63                 return true;
64             }
65             return false;
66         };
67 
68         ad.create();
69         ad.getButton(AlertDialog.BUTTON_POSITIVE).setOnTouchListener(filterTouchListener);
70         ad.setCanceledOnTouchOutside(true);
71 
72         return ad;
73     }
74 
75     /**
76      * Return whether the device is encrypted with legacy full disk encryption. Newer devices
77      * should be using File Based Encryption.
78      *
79      * @return true if device is encrypted
80      */
isFullDiskEncrypted()81     private static boolean isFullDiskEncrypted() {
82         return StorageManager.isNonDefaultBlockEncrypted();
83     }
84 
85     /**
86      * Get a content View for a dialog to confirm that they want to enable a service.
87      *
88      * @param context A valid context
89      * @param info The info about a service
90      * @return A content view suitable for viewing
91      */
createEnableDialogContentView(Context context, AccessibilityServiceInfo info)92     private static View createEnableDialogContentView(Context context,
93             AccessibilityServiceInfo info) {
94         LayoutInflater inflater = (LayoutInflater) context.getSystemService(
95                 Context.LAYOUT_INFLATER_SERVICE);
96 
97         View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
98                 null);
99 
100         TextView encryptionWarningView = (TextView) content.findViewById(
101                 R.id.encryption_warning);
102         if (isFullDiskEncrypted()) {
103             String text = context.getString(R.string.enable_service_encryption_warning,
104                     getServiceName(context, info));
105             encryptionWarningView.setText(text);
106             encryptionWarningView.setVisibility(View.VISIBLE);
107         } else {
108             encryptionWarningView.setVisibility(View.GONE);
109         }
110 
111         TextView capabilitiesHeaderView = (TextView) content.findViewById(
112                 R.id.capabilities_header);
113         capabilitiesHeaderView.setText(context.getString(R.string.capabilities_list_title,
114                 getServiceName(context, info)));
115 
116         LinearLayout capabilitiesView = (LinearLayout) content.findViewById(R.id.capabilities);
117 
118         // This capability is implicit for all services.
119         View capabilityView = inflater.inflate(
120                 com.android.internal.R.layout.app_permission_item_old, null);
121 
122         ImageView imageView = (ImageView) capabilityView.findViewById(
123                 com.android.internal.R.id.perm_icon);
124         imageView.setImageDrawable(context.getDrawable(
125                 com.android.internal.R.drawable.ic_text_dot));
126 
127         TextView labelView = (TextView) capabilityView.findViewById(
128                 com.android.internal.R.id.permission_group);
129         labelView.setText(context.getString(
130                 R.string.capability_title_receiveAccessibilityEvents));
131 
132         TextView descriptionView = (TextView) capabilityView.findViewById(
133                 com.android.internal.R.id.permission_list);
134         descriptionView.setText(
135                 context.getString(R.string.capability_desc_receiveAccessibilityEvents));
136 
137         List<AccessibilityServiceInfo.CapabilityInfo> capabilities =
138                 info.getCapabilityInfos(context);
139 
140         capabilitiesView.addView(capabilityView);
141 
142         // Service-specific capabilities.
143         final int capabilityCount = capabilities.size();
144         for (int i = 0; i < capabilityCount; i++) {
145             AccessibilityServiceInfo.CapabilityInfo capability = capabilities.get(i);
146 
147             capabilityView = inflater.inflate(
148                     com.android.internal.R.layout.app_permission_item_old, null);
149 
150             imageView = (ImageView) capabilityView.findViewById(
151                     com.android.internal.R.id.perm_icon);
152             imageView.setImageDrawable(context.getDrawable(
153                     com.android.internal.R.drawable.ic_text_dot));
154 
155             labelView = (TextView) capabilityView.findViewById(
156                     com.android.internal.R.id.permission_group);
157             labelView.setText(context.getString(capability.titleResId));
158 
159             descriptionView = (TextView) capabilityView.findViewById(
160                     com.android.internal.R.id.permission_list);
161             descriptionView.setText(context.getString(capability.descResId));
162 
163             capabilitiesView.addView(capabilityView);
164         }
165 
166         return content;
167     }
168 
169     // Get the service name and bidi wrap it to protect from bidi side effects.
getServiceName(Context context, AccessibilityServiceInfo info)170     private static CharSequence getServiceName(Context context, AccessibilityServiceInfo info) {
171         final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
172         final CharSequence label =
173                 info.getResolveInfo().loadLabel(context.getPackageManager());
174         return BidiFormatter.getInstance(locale).unicodeWrap(label);
175     }
176 }
177