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