前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Android][Framework] 无障碍快捷方式相关代码

[Android][Framework] 无障碍快捷方式相关代码

作者头像
wOw
发布2018-09-18 14:57:06
1.8K0
发布2018-09-18 14:57:06
举报

问题:无障碍快捷方式(Accessibility Shortcut)打开不生效。

如图,打开功能后,长按power键会出现振动,震动后双指放在屏幕上会打开无障碍。

无障碍的功能从来没有接触过,也不清楚在哪个模块修改,所以下面记录一下如何快速定位这种问题的思路:

在Opengrok检索"Accessibility Shortcut"找到字串accessibility_global_gesture_preference_title,可以确定两个地方:

  • Settings里Accessibility选项的入口 packages/apps/Settings/res/xml/accessibility_settings.xml
  • Accessibility的控制代码 packages/apps/Settings/src/com/android/settings/accessibility/AccessibilitySettings.java
AccessibilitySettings.java

通过阅读方法列表,知道这个类完全是用来控制Settings的Accessibility界面按钮的逻辑。

从控制代码,知道打开上图界面的代码是:

private void handleToggleEnableAccessibilityGesturePreferenceClick() {
    Bundle extras = mGlobalGesturePreferenceScreen.getExtras();
    extras.putString(EXTRA_TITLE, getString(
        R.string.accessibility_global_gesture_preference_title));
    extras.putString(EXTRA_SUMMARY, getString(
        R.string.accessibility_global_gesture_preference_description));
    extras.putBoolean(EXTRA_CHECKED, Settings.Global.getInt(getContentResolver(),Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1);
    super.onPreferenceTreeClick(mGlobalGesturePreferenceScreen);
}

这一步是设置Title和Summary,和设置SwichBar是否check。所以这个KEY是控制变量的关键。

搜索Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED,可以了解到几个地方:

ToggleGlobalGesturePreferenceFragment.java

这里通过KEY的命名和相关类的命名,可以知道,Accessibility Shortcut打开后,相关的手势被称为Global Gesture,全局手势。了解命名也很重要,这对于分析不熟悉的模块很有帮助。

这个Fragment是处理SwichBar的回调。每当SwichBar状态变化,就会更新相关的值:

@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
    Settings.Global.putInt(getContentResolver(),
            Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, enabled ? 1 : 0);
}

前面两个类似乎并不是关键,后面的两个就不一样了。

PhoneWindowManager.java

这是个大类,涉及到非常多的控制逻辑,而且从命名就能知道它的核心功能。但实际上,对于这个问题,只需要看一部分逻辑:

Accessibility Shortcut功能是长按POWER键启用。在PWM中,有powerLongPress的处理逻辑:

private void powerLongPress() {
	final int behavior = getResolvedLongPressOnPowerBehavior();
	switch (behavior) {
		case LONG_PRESS_POWER_NOTHING:
			break;
		case LONG_PRESS_POWER_GLOBAL_ACTIONS:
			mPowerKeyHandled = true;
			if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
				performAuditoryFeedbackForAccessibilityIfNeed();
			}
			showGlobalActionsInternal();
			break;
        case LONG_PRESS_POWER_SHUT_OFF:
        case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
        	mPowerKeyHandled = true;
        	performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
        	sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
        	mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
        	break;
    }
}

首先获取长按POWER键的行为:

private int getResolvedLongPressOnPowerBehavior() {
    if (FactoryTest.isLongPressOnPowerOffEnabled()) {
    	return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM;
    }
    return mLongPressOnPowerBehavior;
}

正常情况下都会直接返回mLongPressOnPowerBehavior,这个变量初始化如下:

mLongPressOnPowerBehavior = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_longPressOnPowerBehavior);

这个值在frameworks/base/core/res/res/values/config.xml配置为1。所以在Switch会走到LONG_PRESS_POWER_GLOBAL_ACTIONS这个case。

在这个case下,首先会调用performHapticFeedbackLW方法,从名称看,是perform触觉反馈LW方法,最终目的是发出振动。

@Override
public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always) {
    if (!mVibrator.hasVibrator()) {
    	return false;
    }
    final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(),
    Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
    if (hapticsDisabled && !always) {
    	return false;
    }
    long[] pattern = null;
    switch (effectId) {
        case HapticFeedbackConstants.LONG_PRESS:
        	pattern = mLongPressVibePattern;
        	break;
        case HapticFeedbackConstants.VIRTUAL_KEY:
        	pattern = mVirtualKeyVibePattern;
        	break;
        case HapticFeedbackConstants.KEYBOARD_TAP:
        	pattern = mKeyboardTapVibePattern;
        	break;
        case HapticFeedbackConstants.CLOCK_TICK:
        	pattern = mClockTickVibePattern;
        	break;
        case HapticFeedbackConstants.CALENDAR_DATE:
        	pattern = mCalendarDateVibePattern;
        	break;
        case HapticFeedbackConstants.SAFE_MODE_DISABLED:
        	pattern = mSafeModeDisabledVibePattern;
        	break;
        case HapticFeedbackConstants.SAFE_MODE_ENABLED:
        	pattern = mSafeModeEnabledVibePattern;
        	break;
        case HapticFeedbackConstants.CONTEXT_CLICK:
        	pattern = mContextClickVibePattern;
        	break;
        default:
        	return false;
    }
    int owningUid;
    String owningPackage;
    if (win != null) {
    	owningUid = win.getOwningUid();
    	owningPackage = win.getOwningPackage();
    } else {
    	owningUid = android.os.Process.myUid();
    	owningPackage = mContext.getOpPackageName();
    }
    if (pattern.length == 1) {
    	// One-shot vibration
    	mVibrator.vibrate(owningUid, owningPackage, pattern[0], VIBRATION_ATTRIBUTES);
    } else {
    	// Pattern vibration
    	mVibrator.vibrate(owningUid, owningPackage, pattern, -1, VIBRATION_ATTRIBUTES);
    }
    return true;
}

如果上面返回为false,则没有振动,代码会进入performAuditoryFeedbackForAccessibilityIfNeed,即播放声音。

private void performAuditoryFeedbackForAccessibilityIfNeed() {
    if (!isGlobalAccessibilityGestureEnabled()) {
        return;
    }
    AudioManager audioManager = (AudioManager) mContext.getSystemService(
        Context.AUDIO_SERVICE);
    if (audioManager.isSilentMode()) {
        return;
    }
    Ringtone ringTone = RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_NOTIFICATION_URI);
    ringTone.setStreamType(AudioManager.STREAM_MUSIC);
    ringTone.play();
}

播放声音前的判断就是 通过获取标志位判断。

private boolean isGlobalAccessibilityGestureEnabled() {
	return Settings.Global.getInt(mContext.getContentResolver(),
		Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1;
}

最后调用的是showGlobalActionsInternal方法

void showGlobalActionsInternal() {
    sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
    if (mGlobalActions == null) {
        mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
    }
    final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();
    mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
    if (keyguardShowing) {
        // since it took two seconds of long press to bring this up,
        // poke the wake lock so they have some time to see the dialog.
        mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
    }
}

所以这里看出,PWM只是处理了长按power键的逻辑。那长按之后,双指触摸屏幕的逻辑在哪控制呢?

GlobalActions.java

这个类蛮有意思的,通过类名知道他是全局的一个操作,里面出现大量的控制代码,而且很多和手势操作相关。有机会可以深入了解一下。

在onStart的地方,通过阅读注释,可以知道,这里处理打开Accessibility后的双指触摸,触摸时不销毁弹出的关机选项对话框。

代码中有一个非常重要的判断,它决定是否进入Accessibility模式。

@Override
protected void onStart() {
    // If global accessibility gesture can be performed, we will take care
    // of dismissing the dialog on touch outside. This is because the dialog
    // is dismissed on the first down while the global gesture is a long press
    // with two fingers anywhere on the screen.
    if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) {
    	mEnableAccessibilityController = new EnableAccessibilityController(mContext,
    	new Runnable() {
    		@Override
    		public void run() {
    			dismiss();
    		}
    	});
    	super.setCanceledOnTouchOutside(false);
    } else {
    	mEnableAccessibilityController = null;
    	super.setCanceledOnTouchOutside(true);
    }

    super.onStart();
}
EnableAccessibilityController.java
public static boolean canEnableAccessibilityViaGesture(Context context) {
    AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
    // Accessibility is enabled and there is an enabled speaking
    // accessibility service, then we have nothing to do.
    if (accessibilityManager.isEnabled()
        && !accessibilityManager.getEnabledAccessibilityServiceList(
            AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) {
        return false;
    }
    // If the global gesture is enabled and there is a speaking service
    // installed we are good to go, otherwise there is nothing to do.
    return Settings.Global.getInt(context.getContentResolver(),
                                  Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1
        && !getInstalledSpeakingAccessibilityServices(context).isEmpty();
}

这里需要判断几层:

  • AccessibilityManager enable
  • AccessibilityManager 服务不为空。这里服务可以是自定义安装的。如果没有可用服务就无从打开。
  • ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED标志位打开,即前面界面的switch按钮打开。
  • talkbak必须安装。因为他需要语音播放一些内容,所以talkbak是必备的。

我遇到的问题就是手机没有集成GMS talkbak,导致Accessibility打开没反应。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • AccessibilitySettings.java
  • ToggleGlobalGesturePreferenceFragment.java
  • PhoneWindowManager.java
  • GlobalActions.java
  • EnableAccessibilityController.java
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档