• 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 static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
20 
21 import android.accessibilityservice.AccessibilityServiceInfo;
22 import android.app.Dialog;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.graphics.drawable.Drawable;
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.Button;
33 import android.widget.ImageView;
34 import android.widget.TextView;
35 import android.widget.Toast;
36 
37 import androidx.annotation.NonNull;
38 import androidx.appcompat.app.AlertDialog;
39 import androidx.core.content.ContextCompat;
40 
41 import com.android.settings.R;
42 
43 import java.util.Locale;
44 
45 /**
46  * Utility class for creating the dialog that asks users for explicit permission for an
47  * accessibility service to access user data before the service is enabled
48  */
49 public class AccessibilityServiceWarning {
50     private static final View.OnTouchListener filterTouchListener = (View v, MotionEvent event) -> {
51         // Filter obscured touches by consuming them.
52         if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)
53                 || ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) {
54             if (event.getAction() == MotionEvent.ACTION_UP) {
55                 Toast.makeText(v.getContext(), R.string.touch_filtered_warning,
56                         Toast.LENGTH_SHORT).show();
57             }
58             return true;
59         }
60         return false;
61     };
62 
63     /**
64      * The interface to execute the uninstallation action.
65      */
66     interface UninstallActionPerformer {
uninstallPackage()67         void uninstallPackage();
68     }
69 
70     /** Returns a {@link Dialog} to be shown to confirm that they want to enable a service. */
createCapabilitiesDialog(@onNull Context context, @NonNull AccessibilityServiceInfo info, @NonNull View.OnClickListener listener, @NonNull UninstallActionPerformer performer)71     public static Dialog createCapabilitiesDialog(@NonNull Context context,
72             @NonNull AccessibilityServiceInfo info, @NonNull View.OnClickListener listener,
73             @NonNull UninstallActionPerformer performer) {
74         final AlertDialog ad = new AlertDialog.Builder(context)
75                 .setView(createEnableDialogContentView(context, info, listener, performer))
76                 .create();
77 
78         Window window = ad.getWindow();
79         WindowManager.LayoutParams params = window.getAttributes();
80         params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
81         window.setAttributes(params);
82         ad.create();
83         ad.setCanceledOnTouchOutside(true);
84 
85         return ad;
86     }
87 
createEnableDialogContentView(Context context, @NonNull AccessibilityServiceInfo info, View.OnClickListener listener, UninstallActionPerformer performer)88     private static View createEnableDialogContentView(Context context,
89             @NonNull AccessibilityServiceInfo info, View.OnClickListener listener,
90             UninstallActionPerformer performer) {
91         LayoutInflater inflater = (LayoutInflater) context.getSystemService(
92                 Context.LAYOUT_INFLATER_SERVICE);
93 
94         View content = inflater.inflate(R.layout.enable_accessibility_service_dialog_content,
95                 null);
96 
97         final Drawable icon;
98         if (info.getResolveInfo().getIconResource() == 0) {
99             icon = ContextCompat.getDrawable(context, R.drawable.ic_accessibility_generic);
100         } else {
101             icon = info.getResolveInfo().loadIcon(context.getPackageManager());
102         }
103 
104         ImageView permissionDialogIcon = content.findViewById(
105                 R.id.permissionDialog_icon);
106         permissionDialogIcon.setImageDrawable(icon);
107 
108         TextView permissionDialogTitle = content.findViewById(R.id.permissionDialog_title);
109         permissionDialogTitle.setText(context.getString(R.string.enable_service_title,
110                 getServiceName(context, info)));
111 
112         Button permissionAllowButton = content.findViewById(
113                 R.id.permission_enable_allow_button);
114         Button permissionDenyButton = content.findViewById(
115                 R.id.permission_enable_deny_button);
116         permissionAllowButton.setOnClickListener(listener);
117         permissionAllowButton.setOnTouchListener(filterTouchListener);
118         permissionDenyButton.setOnClickListener(listener);
119 
120         final Button uninstallButton = content.findViewById(
121                 R.id.permission_enable_uninstall_button);
122         // Shows an uninstall button to help users quickly remove the non-system App due to the
123         // required permissions.
124         if (!AccessibilityUtil.isSystemApp(info)) {
125             uninstallButton.setVisibility(View.VISIBLE);
126             uninstallButton.setOnClickListener(v -> performer.uninstallPackage());
127         }
128         return content;
129     }
130 
131     /** Returns a {@link Dialog} to be shown to confirm that they want to disable a service. */
createDisableDialog(Context context, AccessibilityServiceInfo info, DialogInterface.OnClickListener listener)132     public static Dialog createDisableDialog(Context context,
133             AccessibilityServiceInfo info, DialogInterface.OnClickListener listener) {
134         final AlertDialog dialog = new AlertDialog.Builder(context)
135                 .setTitle(context.getString(R.string.disable_service_title,
136                         info.getResolveInfo().loadLabel(context.getPackageManager())))
137                 .setMessage(context.getString(R.string.disable_service_message,
138                         context.getString(R.string.accessibility_dialog_button_stop),
139                         getServiceName(context, info)))
140                 .setCancelable(true)
141                 .setPositiveButton(R.string.accessibility_dialog_button_stop, listener)
142                 .setNegativeButton(R.string.accessibility_dialog_button_cancel, listener)
143                 .create();
144 
145         return dialog;
146     }
147 
148     // Get the service name and bidi wrap it to protect from bidi side effects.
getServiceName(Context context, AccessibilityServiceInfo info)149     private static CharSequence getServiceName(Context context, AccessibilityServiceInfo info) {
150         final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
151         final CharSequence label =
152                 info.getResolveInfo().loadLabel(context.getPackageManager());
153         return BidiFormatter.getInstance(locale).unicodeWrap(label);
154     }
155 }
156