From b2e104f8a26e00cd2437637c7cc2d3133981ef2f Mon Sep 17 00:00:00 2001 From: Jorim Jaggi Date: Fri, 15 Aug 2014 18:12:36 +0200 Subject: [PATCH] Accessibility actions for lock/phone/camera icon Bug: 15859203 Change-Id: I02c988f4ed985e4381321aeca89cdfee68f44697 --- .../res/layout/keyguard_bottom_area.xml | 5 +- packages/SystemUI/res/values/strings.xml | 8 ++ .../phone/KeyguardBottomAreaView.java | 132 ++++++++++++++---- .../statusbar/phone/PhoneStatusBar.java | 5 + .../policy/AccessibilityController.java | 90 ++++++++++++ 5 files changed, 209 insertions(+), 31 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 8e9b501970445..ca07c87498d60 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -31,7 +31,7 @@ android:layout_gravity="bottom|center_horizontal" android:textStyle="italic" android:textColor="#ffffff" - android:textAppearance="?android:attr/textAppearanceSmall"/> + android:textAppearance="?android:attr/textAppearanceSmall" /> + android:tint="#ffffffff" + android:contentDescription="@string/accessibility_unlock_button" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 1d33d7af43bb8..ed3fa58b2fa4b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -213,6 +213,14 @@ Camera Phone + + Unlock + + unlock + + open phone + + open camera Switch input method button. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 61246b01c6d25..44d8d239bd682 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.os.AsyncTask; +import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.phone.PhoneManager; @@ -36,7 +37,7 @@ import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; -import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.TextView; @@ -44,17 +45,23 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.R; -import com.android.systemui.statusbar.KeyguardIndicationController; -import com.android.systemui.statusbar.policy.FlashlightController; +import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.KeyguardAffordanceView; +import com.android.systemui.statusbar.KeyguardIndicationController; +import com.android.systemui.statusbar.policy.AccessibilityController; +import com.android.systemui.statusbar.policy.FlashlightController; import com.android.systemui.statusbar.policy.PreviewInflater; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; +import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; + /** * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status * text. */ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener, - UnlockMethodCache.OnUnlockMethodChangedListener { + UnlockMethodCache.OnUnlockMethodChangedListener, + AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener { final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView"; @@ -80,6 +87,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private FlashlightController mFlashlightController; private PreviewInflater mPreviewInflater; private KeyguardIndicationController mIndicationController; + private AccessibilityController mAccessibilityController; + private PhoneStatusBar mPhoneStatusBar; private final TrustDrawable mTrustDrawable; @@ -101,6 +110,40 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mTrustDrawable = new TrustDrawable(mContext); } + private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + String label = null; + if (host == mLockIcon) { + label = getResources().getString(R.string.unlock_label); + } else if (host == mCameraImageView) { + label = getResources().getString(R.string.camera_label); + } else if (host == mPhoneImageView) { + label = getResources().getString(R.string.phone_label); + } + info.addAction(new AccessibilityAction(ACTION_CLICK, label)); + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (action == ACTION_CLICK) { + if (host == mLockIcon) { + mPhoneStatusBar.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); + return true; + } else if (host == mCameraImageView) { + launchCamera(); + return true; + } else if (host == mPhoneImageView) { + launchPhone(); + return true; + } + } + return super.performAccessibilityAction(host, action, args); + } + }; + @Override protected void onFinishInflate() { super.onFinishInflate(); @@ -111,7 +154,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mLockIcon = (KeyguardAffordanceView) findViewById(R.id.lock_icon); mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text); watchForCameraPolicyChanges(); - watchForAccessibilityChanges(); updateCameraVisibility(); updatePhoneVisibility(); mUnlockMethodCache = UnlockMethodCache.getInstance(getContext()); @@ -123,6 +165,16 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL inflatePreviews(); mLockIcon.setOnClickListener(this); mLockIcon.setBackground(mTrustDrawable); + mLockIcon.setOnLongClickListener(this); + mCameraImageView.setOnClickListener(this); + mPhoneImageView.setOnClickListener(this); + initAccessibility(); + } + + private void initAccessibility() { + mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate); + mPhoneImageView.setAccessibilityDelegate(mAccessibilityDelegate); + mCameraImageView.setAccessibilityDelegate(mAccessibilityDelegate); } @Override @@ -150,6 +202,15 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mFlashlightController = flashlightController; } + public void setAccessibilityController(AccessibilityController accessibilityController) { + mAccessibilityController = accessibilityController; + accessibilityController.addStateChangedCallback(this); + } + + public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) { + mPhoneStatusBar = phoneStatusBar; + } + private Intent getCameraIntent() { KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); boolean currentUserHasTrust = updateMonitor.getUserHasTrust( @@ -203,28 +264,24 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback); } - private void watchForAccessibilityChanges() { - final AccessibilityManager am = - (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - - // Set the initial state - enableAccessibility(am.isTouchExplorationEnabled()); - - // Watch for changes - am.addTouchExplorationStateChangeListener( - new AccessibilityManager.TouchExplorationStateChangeListener() { - @Override - public void onTouchExplorationStateChanged(boolean enabled) { - enableAccessibility(enabled); - } - }); + @Override + public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) { + mCameraImageView.setClickable(touchExplorationEnabled); + mPhoneImageView.setClickable(touchExplorationEnabled); + mCameraImageView.setFocusable(accessibilityEnabled); + mPhoneImageView.setFocusable(accessibilityEnabled); + updateLockIconClickability(); } - private void enableAccessibility(boolean touchExplorationEnabled) { - mCameraImageView.setOnClickListener(touchExplorationEnabled ? this : null); - mCameraImageView.setClickable(touchExplorationEnabled); - mPhoneImageView.setOnClickListener(touchExplorationEnabled ? this : null); - mPhoneImageView.setClickable(touchExplorationEnabled); + private void updateLockIconClickability() { + if (mAccessibilityController == null) { + return; + } + mLockIcon.setClickable(mUnlockMethodCache.isTrustManaged() + || mAccessibilityController.isTouchExplorationEnabled()); + mLockIcon.setLongClickable(mAccessibilityController.isTouchExplorationEnabled() + && mUnlockMethodCache.isTrustManaged()); + mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled()); } @Override @@ -234,12 +291,27 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } else if (v == mPhoneImageView) { launchPhone(); } if (v == mLockIcon) { - mIndicationController.showTransientIndication( - R.string.keyguard_indication_trust_disabled); - mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser()); + if (!mAccessibilityController.isAccessibilityEnabled()) { + handleTrustCircleClick(); + } else { + mPhoneStatusBar.animateCollapsePanels( + CommandQueue.FLAG_EXCLUDE_NONE, true /* force */); + } } } + @Override + public boolean onLongClick(View v) { + handleTrustCircleClick(); + return true; + } + + private void handleTrustCircleClick() { + mIndicationController.showTransientIndication( + R.string.keyguard_indication_trust_disabled); + mLockPatternUtils.requireCredentialEntry(mLockPatternUtils.getCurrentUser()); + } + public void launchCamera() { mFlashlightController.killFlashlight(); Intent intent = getCameraIntent(); @@ -304,7 +376,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mLockIcon.setImageResource(iconRes); boolean trustManaged = mUnlockMethodCache.isTrustManaged(); mTrustDrawable.setTrustManaged(trustManaged); - mLockIcon.setClickable(trustManaged); + + // TODO: Update content description depending on state + updateLockIconClickability(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 502c37d546220..652d348afb222 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -132,6 +132,7 @@ import com.android.systemui.statusbar.SignalClusterView; import com.android.systemui.statusbar.SpeedBumpView; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.StatusBarState; +import com.android.systemui.statusbar.policy.AccessibilityController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; import com.android.systemui.statusbar.policy.BluetoothControllerImpl; @@ -237,6 +238,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, NextAlarmController mNextAlarmController; KeyguardMonitor mKeyguardMonitor; BrightnessMirrorController mBrightnessMirrorController; + AccessibilityController mAccessibilityController; int mNaturalBarHeight = -1; int mIconSize = -1; @@ -805,6 +807,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mFlashlightController = new FlashlightController(mContext); mKeyguardBottomArea.setFlashlightController(mFlashlightController); + mKeyguardBottomArea.setPhoneStatusBar(this); + mAccessibilityController = new AccessibilityController(mContext); + mKeyguardBottomArea.setAccessibilityController(mAccessibilityController); mNextAlarmController = new NextAlarmController(mContext); mKeyguardMonitor = new KeyguardMonitor(); mUserSwitcherController = new UserSwitcherController(mContext, mKeyguardMonitor); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java new file mode 100644 index 0000000000000..89ed78723e7ee --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessibilityController.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.policy; + +import android.content.Context; +import android.util.Log; +import android.view.accessibility.AccessibilityManager; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + +public class AccessibilityController implements + AccessibilityManager.AccessibilityStateChangeListener, + AccessibilityManager.TouchExplorationStateChangeListener { + + private final ArrayList mChangeCallbacks = new ArrayList<>(); + + private boolean mAccessibilityEnabled; + private boolean mTouchExplorationEnabled; + + public AccessibilityController(Context context) { + AccessibilityManager am = + (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); + am.addTouchExplorationStateChangeListener(this); + am.addAccessibilityStateChangeListener(this); + mAccessibilityEnabled = am.isEnabled(); + mTouchExplorationEnabled = am.isTouchExplorationEnabled(); + } + + public boolean isAccessibilityEnabled() { + return mAccessibilityEnabled; + } + + public boolean isTouchExplorationEnabled() { + return mTouchExplorationEnabled; + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("AccessibilityController state:"); + pw.print(" mAccessibilityEnabled="); pw.println(mAccessibilityEnabled); + pw.print(" mTouchExplorationEnabled="); pw.println(mTouchExplorationEnabled); + } + + public void addStateChangedCallback(AccessibilityStateChangedCallback cb) { + mChangeCallbacks.add(cb); + cb.onStateChanged(mAccessibilityEnabled, mTouchExplorationEnabled); + } + + public void removeStateChangedCallback(AccessibilityStateChangedCallback cb) { + mChangeCallbacks.remove(cb); + } + + private void fireChanged() { + final int N = mChangeCallbacks.size(); + for (int i = 0; i < N; i++) { + mChangeCallbacks.get(i).onStateChanged(mAccessibilityEnabled, mTouchExplorationEnabled); + } + } + + @Override + public void onAccessibilityStateChanged(boolean enabled) { + mAccessibilityEnabled = enabled; + fireChanged(); + } + + @Override + public void onTouchExplorationStateChanged(boolean enabled) { + mTouchExplorationEnabled = enabled; + fireChanged(); + } + + public interface AccessibilityStateChangedCallback { + void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled); + } +}