1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.chrome.browser;
6
7import android.accessibilityservice.AccessibilityServiceInfo;
8import android.app.AlertDialog;
9import android.content.Context;
10import android.content.DialogInterface;
11import android.content.Intent;
12import android.content.pm.PackageInfo;
13import android.content.pm.PackageManager;
14import android.net.Uri;
15import android.os.Build;
16import android.view.accessibility.AccessibilityManager;
17
18import org.chromium.base.CalledByNative;
19import org.chromium.chrome.R;
20
21import java.util.List;
22
23/**
24 * Exposes information about the current accessibility state
25 */
26public class AccessibilityUtil {
27    // Whether we've already shown an alert that they have an old version of TalkBack running.
28    private static boolean sOldTalkBackVersionAlertShown = false;
29
30    // The link to download or update TalkBack from the Play Store.
31    private static final String TALKBACK_MARKET_LINK =
32            "market://search?q=pname:com.google.android.marvin.talkback";
33
34    // The package name for TalkBack, an Android accessibility service.
35    private static final String TALKBACK_PACKAGE_NAME =
36            "com.google.android.marvin.talkback";
37
38    // The minimum TalkBack version that we support. This is the version that shipped with
39    // KitKat, from fall 2013. Versions older than that should be updated.
40    private static final int MIN_TALKBACK_VERSION = 105;
41
42    private AccessibilityUtil() { }
43
44    /**
45     * Checks to see that this device has accessibility and touch exploration enabled.
46     * @param context A {@link Context} instance.
47     * @return        Whether or not accessibility and touch exploration are enabled.
48     */
49    @CalledByNative
50    public static boolean isAccessibilityEnabled(Context context) {
51        AccessibilityManager manager = (AccessibilityManager)
52                context.getSystemService(Context.ACCESSIBILITY_SERVICE);
53        return manager != null && manager.isEnabled() && manager.isTouchExplorationEnabled();
54    }
55
56    /**
57     * Checks to see if an old version of TalkBack is running that Chrome doesn't support,
58     * and if so, shows an alert dialog prompting the user to update the app.
59     * @param context A {@link Context} instance.
60     * @return        True if the dialog was shown.
61     */
62    public static boolean showWarningIfOldTalkbackRunning(Context context) {
63        AccessibilityManager manager = (AccessibilityManager)
64                context.getSystemService(Context.ACCESSIBILITY_SERVICE);
65        if (manager == null) return false;
66
67        boolean isTalkbackRunning = false;
68        try {
69            List<AccessibilityServiceInfo> services =
70                    manager.getEnabledAccessibilityServiceList(
71                            AccessibilityServiceInfo.FEEDBACK_SPOKEN);
72            for (AccessibilityServiceInfo service : services) {
73                if (service.getId().contains(TALKBACK_PACKAGE_NAME)) isTalkbackRunning = true;
74            }
75        } catch (NullPointerException e) {
76            // getEnabledAccessibilityServiceList() can throw an NPE due to a bad
77            // AccessibilityService.
78        }
79        if (!isTalkbackRunning) return false;
80
81        try {
82            PackageInfo talkbackInfo = context.getPackageManager().getPackageInfo(
83                    TALKBACK_PACKAGE_NAME, 0);
84            if (talkbackInfo != null && talkbackInfo.versionCode < MIN_TALKBACK_VERSION &&
85                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN &&
86                    !sOldTalkBackVersionAlertShown) {
87                showOldTalkbackVersionAlertOnce(context);
88                return true;
89            }
90        } catch (PackageManager.NameNotFoundException e) {
91            // Do nothing, default to false.
92        }
93
94        return false;
95    }
96
97    private static void showOldTalkbackVersionAlertOnce(final Context context) {
98        if (sOldTalkBackVersionAlertShown) return;
99        sOldTalkBackVersionAlertShown = true;
100
101        AlertDialog.Builder builder = new AlertDialog.Builder(context)
102            .setTitle(R.string.old_talkback_title)
103            .setPositiveButton(R.string.update_from_market,
104                    new DialogInterface.OnClickListener() {
105                        @Override
106                        public void onClick(DialogInterface dialog, int id) {
107                            Uri marketUri = Uri.parse(TALKBACK_MARKET_LINK);
108                            Intent marketIntent = new Intent(
109                                    Intent.ACTION_VIEW, marketUri);
110                            context.startActivity(marketIntent);
111                        }
112                    })
113            .setNegativeButton(R.string.cancel_talkback_alert,
114                    new DialogInterface.OnClickListener() {
115                        @Override
116                        public void onClick(DialogInterface dialog, int id) {
117                            // Do nothing, this alert is only shown once either way.
118                        }
119                    });
120        AlertDialog dialog = builder.create();
121        dialog.show();
122    }
123}
124