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