口袋模式
口袋模式:带有黑屏手势功能的手机,一般都需要进行一个防误触的判断。防止手机放在口袋里或者包包(一些导电介质的东西可能带来辅助效果,比如钥匙)里别误触发点亮屏幕,甚至是打出电话。
口袋模式设计原理:接近式传感器的靠近与远离功能。可以参考我们手机打电话时,耳朵贴近手机的时候,往往屏幕是自动息屏,离开耳朵一小段距离,屏幕又亮起的这个功能。同理和我们放在口袋中一个意思。
概要设计
-
添加设置的各级菜单
-
获取距离传感器以及功能实现
功能实现
添加各级菜单
各级菜单的定义以及其界面的实现,主要是对交互界面的实现,除数据库的存储外,不涉及到逻辑的处理。
一、添加设置的各级菜单
添加模块:MtkSettings
添加菜单
路径: res/xml/gestures.xml
<com.android.settingslib.PrimarySwitchPreference android:key="pocket_mode" android:title="@string/pocket_mode" android:summary="@string/pocket_mode_tip" settings:controller="com.android.settings.gestures.PocketModePreferenceController" />
添加字符串资源
路径:res/values/strings.xml
<string name="pocket_mode">Pocket Mode</string> <string name="pocket_mode_tip">It can help users avoid accidental touch of the screen in their pockets or bags</string>
添加PocketModePreferenceController.java类
路径:这个类需要在gestures目录上自行添加。src/com/android/settings/gestures/PocketModePreferenceController.java
package com.android.settings.gestures; import static android.provider.Settings.Secure.TURN_ON_BACKLIGHT_GESTURE; import android.content.ContentResolver; import android.content.Context; import android.net.Uri; import android.os.Handler; import android.os.SystemProperties; import android.provider.Settings; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; import com.google.common.annotations.VisibleForTesting; import java.io.DataOutputStream; import java.io.IOException; import android.database.ContentObserver; public class PocketModePreferenceController extends TogglePreferenceController implements LifecycleObserver, OnStart, OnStop { @VisibleForTesting static final int KEY_CHORD_POWER_VOLUME_UP_MUTE_TOGGLE = 1; private Preference mPreference; private SettingObserver mSettingObserver; public PocketModePreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mPreference = screen.findPreference(getPreferenceKey()); mSettingObserver = new SettingObserver(mPreference); } @Override public boolean isChecked() { final int status = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.POCKET_MODE,0); return status != 0; } @Override public boolean setChecked(boolean isChecked) { return Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.POCKET_MODE, isChecked ? 1 : 0); } @Override public void updateState(Preference preference) { super.updateState(preference); CharSequence summary = mContext.getText(R.string.pocket_mode_tip); preference.setSummary(summary); } @Override public int getAvailabilityStatus() { return SystemProperties.getBoolean("ro.config.wake_gesture", false) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @Override public void onStart() { if (mSettingObserver != null) { mSettingObserver.register(mContext.getContentResolver()); mSettingObserver.onChange(false, null); } } @Override public void onStop() { if (mSettingObserver != null) { mSettingObserver.unregister(mContext.getContentResolver()); } } private class SettingObserver extends ContentObserver { private final Uri mGestureUri = Settings.Secure.getUriFor( Settings.Secure.POCKET_MODE); private final Preference mPreference; SettingObserver(Preference preference) { super(new Handler()); mPreference = preference; } public void register(ContentResolver cr) { cr.registerContentObserver(mGestureUri, false, this); } public void unregister(ContentResolver cr) { cr.unregisterContentObserver(this); } @Override public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange, uri); if (uri == null || mGestureUri.equals(uri)) { updateState(mPreference); } } } @Override public int getSliceHighlightMenuRes() { return 0; } }
在getAvailabilityStatus()这个方法中,使用了宏控,可以根据实际需求自行选择开关的状态。各级菜单的定义以及其界面的实现,主要是对交互界面的实现,除数据库的存储外,不涉及到逻辑的处理。
二、获取距离传感器以及功能实现
添加模块:framework/base
目前智能亮屏方式有两种,抬腕亮屏和双击亮屏。口袋模式的实现方式则是针对这两种亮屏方式进行实现。当口袋模式打开的时候,pSensor如果检测到有物体靠近,就使抬腕亮屏和双击亮屏功能失效,否则,抬腕亮屏和双击亮屏功能正常实现。
设置数据库属性
路径: core/java/android/provider/Settings.java
/** * @hide */ public static final String POCKET_MODE = "pocket_mode";
获取距离的传感器ProximityCheck.java类
路径:services/core/java/com/android/server/policy/ProximityCheck.java
package com.android.server.policy; import android.view.KeyEvent; import android.os.Handler; import android.util.Slog; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorManager; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.os.PowerManager; import android.os.PowerManager.WakeReason; import android.os.SystemClock; public class ProximityCheck { private final SensorManager mSensorManager; private KeyEvent event; private boolean mRegistered; private final Handler mHandler; static final String TAG = "ProximityCheck"; PowerManager mPowerManager; ProximityCheck(Context context, Handler handler) { mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); mPowerManager= (PowerManager) context.getSystemService(Context.POWER_SERVICE); mHandler = handler; } private final SensorEventListener mSensorEventListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { if (event.values.length == 0) { Slog.w(TAG, "ProximityCheck Event has no values!"); finishWithResult(false); } else { boolean isNear = event.values[0] != 1.0f; Slog.d(TAG, "ProximityCheck isNear:" + isNear+" "+event.values[0]); finishWithResult(isNear); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } }; private final Runnable mTimeoutRunnable = () -> { Slog.w(TAG, "ProximityCheck timeout!"); finishWithResult(false); }; public void onResult(boolean isNear) { Slog.w(TAG, "ProximityCheck onResult:"+isNear); if(isNear){ mPowerManager.goToSleep(SystemClock.uptimeMillis()); } } public void check() { Slog.d(TAG, "ProximityCheck check"); mRegistered = true; mSensorManager.registerListener(mSensorEventListener, mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY), SensorManager.SENSOR_DELAY_NORMAL, mHandler); mHandler.postDelayed(mTimeoutRunnable, 500); } private void finishWithResult(boolean isNear) { onResult(isNear); if (mRegistered) { mRegistered = false; mHandler.removeCallbacks(mTimeoutRunnable); mSensorManager.unregisterListener(mSensorEventListener); } } }
口袋模式功能的实现PhoneWindowManager.java
路径:services/core/java/com/android/server/policy/PhoneWindowManager.java
1、声明ProximityCheck。
private ProximityCheck proximityCheck;
2、抬腕唤醒时,检测是否有物体靠近
在抬腕亮屏的onWakeUp方法中创建ProximityCheck对象,并获取到口袋模式的开关是否打开,如果打开则是调用ProximityCheck对象中的check方法。
{ if(proximityCheck == null ){ proximityCheck = new ProximityCheck(mContext, mHandler); } if (shouldEnableWakeGestureLp()) { performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false, "Wake Up"); wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromWakeGesture, PowerManager.WAKE_REASON_GESTURE, "android.policy:GESTURE"); boolean pocketMode = Settings.Secure.getInt(mContext.getContentResolver(),Settings.Secure.POCKET_MODE,0) != 0; if(pocketMode) { proximityCheck.check(); } } }
3、双击唤醒时,检测是否有物体靠近
在interceptKeyBeforeQueueing方法中上报各种keycode的下面添加一个双击亮屏的case KeyEvent.keycode,然后创建ProximityCheck对象,并获取到口袋模式的开关是否打开,如果打开则是调用ProximityCheck对象中的check方法。
case KeyEvent.KEYCODE_U: if(proximityCheck == null ){ proximityCheck = new ProximityCheck(mContext, mHandler); } if(down){ boolean pocketMode = Settings.Secure.getInt(mContext.getContentResolver(),Settings.Secure.POCKET_MODE,0) != 0; mPowerManager.wakeUp(event.getEventTime(), PowerManager.WAKE_REASON_WAKE_KEY, "android:policy:double_click"); if(pocketMode){ proximityCheck.check(); } } break;