• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.internal.accessibility.dialog;
18 
19 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
20 
21 import android.accessibilityservice.AccessibilityServiceInfo;
22 import android.annotation.SuppressLint;
23 import android.app.AlertDialog;
24 import android.content.Context;
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 
39 import com.android.internal.R;
40 import com.android.internal.annotations.VisibleForTesting;
41 
42 import java.util.Locale;
43 
44 /**
45  * Utility class for creating the dialog that asks the user for explicit permission
46  * before an accessibility service is enabled.
47  */
48 public class AccessibilityServiceWarning {
49 
50     /**
51      * Returns an {@link AlertDialog} to be shown to confirm that the user
52      * wants to enable an {@link android.accessibilityservice.AccessibilityService}.
53      */
createAccessibilityServiceWarningDialog(@onNull Context context, @NonNull AccessibilityServiceInfo info, @NonNull View.OnClickListener allowListener, @NonNull View.OnClickListener denyListener, @NonNull View.OnClickListener uninstallListener)54     public static AlertDialog createAccessibilityServiceWarningDialog(@NonNull Context context,
55             @NonNull AccessibilityServiceInfo info,
56             @NonNull View.OnClickListener allowListener,
57             @NonNull View.OnClickListener denyListener,
58             @NonNull View.OnClickListener uninstallListener) {
59         final AlertDialog ad = new AlertDialog.Builder(context)
60                 .setView(createAccessibilityServiceWarningDialogContentView(
61                         context, info, allowListener, denyListener, uninstallListener))
62                 .setCancelable(true)
63                 .create();
64         Window window = ad.getWindow();
65         WindowManager.LayoutParams params = window.getAttributes();
66         params.privateFlags |= SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
67         window.setAttributes(params);
68         return ad;
69     }
70 
71     @VisibleForTesting
createAccessibilityServiceWarningDialogContentView(Context context, AccessibilityServiceInfo info, View.OnClickListener allowListener, View.OnClickListener denyListener, View.OnClickListener uninstallListener)72     public static View createAccessibilityServiceWarningDialogContentView(Context context,
73             AccessibilityServiceInfo info,
74             View.OnClickListener allowListener,
75             View.OnClickListener denyListener,
76             View.OnClickListener uninstallListener) {
77         final LayoutInflater inflater = context.getSystemService(LayoutInflater.class);
78         final View content = inflater.inflate(R.layout.accessibility_service_warning, null);
79 
80         final Drawable icon;
81         if (info.getResolveInfo().getIconResource() == 0) {
82             icon = context.getDrawable(R.drawable.ic_accessibility_generic);
83         } else {
84             icon = info.getResolveInfo().loadIcon(context.getPackageManager());
85         }
86         final ImageView permissionDialogIcon = content.findViewById(
87                 R.id.accessibility_permissionDialog_icon);
88         permissionDialogIcon.setImageDrawable(icon);
89 
90         final TextView permissionDialogTitle = content.findViewById(
91                 R.id.accessibility_permissionDialog_title);
92         permissionDialogTitle.setText(context.getString(R.string.accessibility_enable_service_title,
93                 getServiceName(context, info)));
94 
95         final Button permissionAllowButton = content.findViewById(
96                 R.id.accessibility_permission_enable_allow_button);
97         final Button permissionDenyButton = content.findViewById(
98                 R.id.accessibility_permission_enable_deny_button);
99         permissionAllowButton.setOnClickListener(allowListener);
100         permissionAllowButton.setOnTouchListener(getTouchConsumingListener());
101         permissionDenyButton.setOnClickListener(denyListener);
102 
103         final Button uninstallButton = content.findViewById(
104                 R.id.accessibility_permission_enable_uninstall_button);
105         // Show an uninstall button to help users quickly remove non-preinstalled apps.
106         if (!info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp()) {
107             uninstallButton.setVisibility(View.VISIBLE);
108             uninstallButton.setOnClickListener(uninstallListener);
109         }
110         return content;
111     }
112 
113     @VisibleForTesting
114     @SuppressLint("ClickableViewAccessibility") // Touches are intentionally consumed
getTouchConsumingListener()115     public static View.OnTouchListener getTouchConsumingListener() {
116         return (view, event) -> {
117             // Filter obscured touches by consuming them.
118             if (((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0)
119                     || ((event.getFlags() & MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED) != 0)) {
120                 if (event.getAction() == MotionEvent.ACTION_UP) {
121                     Toast.makeText(view.getContext(),
122                             R.string.accessibility_dialog_touch_filtered_warning,
123                             Toast.LENGTH_SHORT).show();
124                 }
125                 return true;
126             }
127             return false;
128         };
129     }
130 
131     // Get the service name and bidi wrap it to protect from bidi side effects.
132     private static CharSequence getServiceName(Context context, AccessibilityServiceInfo info) {
133         final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
134         final CharSequence label =
135                 info.getResolveInfo().loadLabel(context.getPackageManager());
136         return BidiFormatter.getInstance(locale).unicodeWrap(label);
137     }
138 }
139