口袋模式
之前有做过一个给手机添加口袋模式的功能,在settings里做一个控制开关,现在回顾记录一下。
先加个Setting secure属性值用于记录口袋模式状态
frameworks/base / core/java/android/provider/Settings.java
/**
* DISABLE POCKET MODE
* 1 don't do close proximity detection on lock screen
* 0 do close proximity detection on lock screen
* @hide
*/
@Readable
public static final String DISABLE_POCKET_MODE = "disable_pocket_mode";
frameworks/base / packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
Settings.Secure.DISABLE_POCKET_MODE,
frameworks/base / packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
VALIDATORS.put(Secure.DISABLE_POCKET_MODE, BOOLEAN_VALIDATOR);
frameworks/base / packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
loadSecureSettings:
//disable pocket mode
loadBooleanSetting(stmt, Settings.Secure.DISABLE_POCKET_MODE,
R.bool.def_disable_pocket_mode);
frameworks/base / packages/SettingsProvider/res/values/defaults.xml
<!-- Default for Settings.Secure.DISABLE_POCKET_MODE -->
<bool name="def_disable_pocket_mode">false</bool>
frameworks/base / services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
SECURE_SETTINGS_DEVICEOWNER_ALLOWLIST.add(Settings.Secure.DISABLE_POCKET_MODE);
在SystemUI里监听P-sensor,设置个遮盖页面
frameworks/base / packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
import com.android.systemui.shade.NotificationPanelView;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.widget.RelativeLayout;
private static final int HINT_RESET_DELAY_MS = 1200;
+private static final int PROXIMITY_DEBOUNCE_MS = 30;
protected NotificationPanelViewController mNotificationPanelViewController;
+protected RelativeLayout mProximityDetectedView;
+protected NotificationPanelView mNotificationPanelView;
makeStatusBarView:
mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
R.id.ambient_indication_container);
+mProximityDetectedView = mNotificationShadeWindowView.findViewById(
R.id.proximity_view);
+mNotificationPanelView = mNotificationShadeWindowView.findViewById(
R.id.notification_panel);
showKeyguard:
updateIsKeyguard();
+enableProximityDetectionIfNecessary();
hideKeyguardImpl:
+disableProximityDetectionState();
+restoreProximityWakelockIfNecessary();
Trace.endSection();
private Handler proximityDebounceHandler = new Handler();
private SensorEventListener sensorEventListener = new SensorEventListener() {
@Override
public final void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
public final void onSensorChanged(SensorEvent event) {
proximityDebounceHandler.removeCallbacksAndMessages(null);
boolean isPn = isKeyguardProximityDetectionNecessary();
if (!isPn) {
disableProximityDetectionState();
return;
}
final boolean proximityDetected = event.values[0] == 0.0;
// For initial proximity state, set right awayvalue
if (mProximityDetected == null) {
setProximityDetectedShowing(proximityDetected);
return;
}
proximityDebounceHandler.postDelayed(new Runnable() {
@Override
public void run() {
setProximityDetectedShowing(proximityDetected);
}
}, PROXIMITY_DEBOUNCE_MS);
}
};
private SensorManager sensorManager;
private Boolean mProximityDetected = null;
private boolean mProximityListening;
public boolean getProximityDetected() {
return mProximityDetected == null ? false :mProximityDetected.booleanValue();
}
private void registerSensorListener() {
if (mProximityListening) return;
sensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
Sensor proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
sensorManager.registerListener(sensorEventListener, proximity, SensorManager.SENSOR_DELAY_NORMAL);
mProximityListening = true;
}
private boolean isKeyguardProximityDetectionNecessary() {
boolean isDreaming = false;
try {
isDreaming = mDreamManager.isDreaming();
} catch (RemoteException e) {
}
return
isKeyguardShowing()
&& mExpandedVisible //mStatusBarWindowController.isCurrentlyExpanded()
&& !mDozing
&& !mDozeServiceHost.getDozingRequested()//mDozingRequested
&& !isGoingToSleepOrAsleep()
&& !isDreaming
&& mDeviceProvisionedController.isDeviceProvisioned()
&& (mUserSetup || mUserSwitcherController == null || !mUserSwitcherController.isSimpleUserSwitcher());
}
private void enableProximityDetectionIfNecessary() {
int pocket_mode = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.DISABLE_POCKET_MODE, 0);
if (pocket_mode == 0){
return;
}
if (isKeyguardProximityDetectionNecessary()) {
registerSensorListener();
mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_POWER_BUTTON, "android.policy:POWER");
}
}
private void restoreProximityWakelockIfNecessary() {
boolean isDreaming = false;
try {
isDreaming = mDreamManager.isDreaming();
} catch (RemoteException e) {
}
if (!isGoingToSleepOrAsleep() && !mDozing && !mDozeServiceHost.getDozingRequested()/*mDozingRequested*/ && !isDreaming) {
mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_UNKNOWN, "android.policy:RESTORE_PROXIMITY_WAKEFULNESS");
}
}
private void unregisterSensorListener() {
mProximityDetected = null;
if (!mProximityListening) return;
if (sensorManager == null) return;
try {
sensorManager.unregisterListener(sensorEventListener);
} catch (Exception e) {
}
mProximityListening = false;
}
private void disableProximityDetectionState() {
unregisterSensorListener();
proximityDebounceHandler.removeCallbacksAndMessages(null);
setProximityDetectedShowing(false);
}
private void setProximityDetectedShowing(boolean show) {
mProximityDetected = show;
if (show) {
onBackPressed();
mNotificationPanelView.setVisibility(View.GONE);
mNotificationPanelView.setAlpha(0f);
mProximityDetectedView.setVisibility(View.VISIBLE);
} else {
mNotificationPanelView.setAlpha(1f);
mNotificationPanelView.setVisibility(View.VISIBLE);
mProximityDetectedView.setVisibility(View.GONE);
}
}
onFinishedWakingUp:
mWakeUpCoordinator.setWakingUp(false);
+enableProximityDetectionIfNecessary();
onScreenTurnedOff:
+disableProximityDetectionState();
isGoingToSleep:
private boolean isGoingToSleepOrAsleep() {
int wakefulness = mWakefulnessLifecycle.getWakefulness();
return wakefulness == WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP || wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
}
frameworks/base / packages/SystemUI/res/layout/status_bar_proximity_detected.xml
+xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- This is the view that appears when proximity is detected on the lock screen -->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:id="@+id/proximity_view"
>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="?attr/wallpaperTextColor"
android:text="@string/proximity_detected"
/>
</RelativeLayout>
frameworks/base / packages/SystemUI/res/layout/super_notification_shade.xml
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
+<include layout="@layout/status_bar_proximity_detected"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
frameworks/base / packages/SystemUI/res/values/strings.xml
+<string name="proximity_detected">Close proximity detected</string>
在Settings里添加一个开关
+packages/apps/Settings / src/com/android/settings/display/PocketModePreferenceController.java
package com.android.settings.display;
import static android.provider.Settings.Secure.DISABLE_POCKET_MODE;
import android.content.Context;
import android.provider.Settings;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
public class PocketModePreferenceController extends AbstractPreferenceController implements
PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String KEY_POCKET_MODE = "pocket_mode";
public PocketModePreferenceController(Context context) {
super(context);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return KEY_POCKET_MODE;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
boolean value = (Boolean) newValue;
Settings.Secure.putInt(mContext.getContentResolver(), DISABLE_POCKET_MODE, value ? 1 : 0);
return true;
}
@Override
public void updateState(Preference preference) {
int value = Settings.Secure.getInt(mContext.getContentResolver(), DISABLE_POCKET_MODE, 0);
((SwitchPreference) preference).setChecked(value != 0);
}
}
packages/apps/Settings / src/com/android/settings/DisplaySettings.java
+import com.android.settings.display.PocketModePreferenceController;
buildPreferenceControllers:
+controllers.add(new PocketModePreferenceController(context));
packages/apps/Settings / res/xml/display_settings.xml
<SwitchPreference
android:key="pocket_mode"
android:title="@string/pocket_mode"/>
packages/apps/Settings / res/values/strings.xml
<string name="pocket_mode">Pocket mode</string>
到这里一个简单的pocket mode算做好了。
然而客户想整个滑动的动画去控制以及来电话时的遮盖处理等,所以后续又升级了一下:
frameworks/base / packages/SystemUI/res/layout/status_bar_proximity_detected.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- This is the view that appears when proximity is detected on the lock screen -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/proximity_view"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:layout_marginTop="100dp"
android:textColor="?attr/wallpaperTextColor"
android:text="@string/proximity_top"
android:textSize="22sp"
android:singleLine="true"
android:ellipsize="end"
/>
<ImageView
android:id="@+id/pocket_mode_iv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="8"
android:gravity="center"
android:layout_marginTop="15dp"
android:background="@android:color/transparent"
android:src="@mipmap/pocket_mode"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:layout_marginTop="20dp"
android:textColor="?attr/wallpaperTextColor"
android:text="@string/proximity_mode"
android:textSize="18sp"
android:singleLine="true"
android:ellipsize="end"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:layout_marginTop="10dp"
android:textColor="?attr/wallpaperTextColor"
android:text="@string/proximity_action"
android:textSize="16sp"
android:singleLine="true"
android:ellipsize="end"
/>
<com.android.systemui.ripple.SpreadView
android:id="@+id/spreadview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
app:spread_center_color="#31DE80"
app:spread_delay_milliseconds="800"
app:spread_distance="3"
app:spread_max_radius="30"
app:spread_radius="70"
android:layout_gravity="center"
app:spread_spread_color="#fff" />
</LinearLayout>
frameworks/base / packages/SystemUI/res/mipmap-mdpi/pocket_mode.png
这个图根据需求来
自定义动画:frameworks/base / packages/SystemUI/src/com/android/systemui/ripple/SpreadView.java
package com.android.systemui.ripple;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import android.annotation.Nullable;
import java.util.List;
import java.util.ArrayList;
import android.content.res.TypedArray;
import com.android.systemui.R;
import android.util.Log;
import android.view.MotionEvent;
import android.provider.Settings;
import android.os.UserHandle;
import android.content.Intent;
/**
* author : suntianhai
* e-mail : tianhai.sun@t2mobile.com
* time : 2023/09/04
* desc : add for pocket mode
* version: 1.0
*/
public class SpreadView extends View {
private Paint centerPaint;
private int radius = 100;
private Paint spreadPaint;
private float centerX;
private float centerY;
private int distance = 3;
private int maxRadius = 50;
private int delayMilliseconds = 100;
private List<Integer> spreadRadius = new ArrayList<>();
private List<Integer> alphas = new ArrayList<>();
public SpreadView(Context context) {
this(context,null,0);
}
public SpreadView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public SpreadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SpreadView, defStyleAttr, 0);
radius = a.getInt(R.styleable.SpreadView_spread_radius, radius);
maxRadius = a.getInt(R.styleable.SpreadView_spread_max_radius, maxRadius);
int centerColor = a.getColor(R.styleable.SpreadView_spread_center_color, android.R.attr.colorAccent);
int spreadColor = a.getColor(R.styleable.SpreadView_spread_spread_color, android.R.attr.colorAccent);
distance = a.getInt(R.styleable.SpreadView_spread_distance, distance);
a.recycle();
centerPaint = new Paint();
centerPaint.setColor(centerColor);
centerPaint.setAntiAlias(true);
alphas.add(255);
spreadRadius.add(0);
spreadPaint = new Paint();
spreadPaint.setAntiAlias(true);
spreadPaint.setAlpha(255);
spreadPaint.setColor(spreadColor);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
centerX = w / 2;
centerY = h / 2;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < spreadRadius.size(); i++) {
int alpha = alphas.get(i);
spreadPaint.setAlpha(alpha);
int width = spreadRadius.get(i);
canvas.drawCircle(centerX, centerY, radius + width, spreadPaint);
if (alpha > 0 && width < 200) {
alpha = (alpha - distance) > 0 ? (alpha - distance) : 1;
alphas.set(i, alpha);
spreadRadius.set(i, width + distance);
}
}
if (spreadRadius.get(spreadRadius.size() - 1) > maxRadius) {
spreadRadius.add(0);
alphas.add(255);
}
if (spreadRadius.size() > 2) {
alphas.remove(0);
spreadRadius.remove(0);
}
canvas.drawCircle(centerX, centerY, radius, centerPaint);
postInvalidateDelayed(delayMilliseconds);
}
private int lastX;
private int lastY;
private boolean closePS = false;
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_UP:
if(closePS){
closePS = false;
layout(0 , 2351, 1224 , 2700);
Settings.Global.putStringForUser(getContext().getContentResolver(),
Settings.Global.UPDATE_BATTERY_CHARGING_MODE, System.currentTimeMillis() + "",
UserHandle.myUserId());
}
break;
case MotionEvent.ACTION_MOVE:
int offsetX = x - lastX;
int offsetY = y - lastY;
layout(getLeft() + offsetX, getTop(), getRight() + offsetX, getBottom());
if(Math.abs(offsetX) > 20){
closePS = true;
}
break;
}
return true;
}
}
frameworks/base / packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
import android.widget.LinearLayout;
import android.database.ContentObserver;
import android.provider.Settings.Global;
import com.android.systemui.ripple.SpreadView;
import android.telephony.TelephonyManager;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import android.view.Window;
import android.app.Dialog;
import android.view.WindowManager.LayoutParams;
import android.view.LayoutInflater;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
private NotificationListContainer mNotifListContainer;
+private boolean isDismissDialog = false;
+final ContentObserver mDisableProximityObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange, Uri uri) {
isDismissDialog = true;
disableProximityDetectionState();
}
};
protected LinearLayout mProximityDetectedView;
protected View spreadview;
protected SystemUIDialog mPocketModeDialog;
start:
+mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mContext.getContentResolver().registerContentObserver(
Global.getUriFor(Global.UPDATE_BATTERY_CHARGING_MODE),
false,
mDisableProximityObserver);
mNotificationPanelView = mNotificationShadeWindowView.findViewById(
R.id.notification_panel);
+spreadview = mProximityDetectedView.findViewById(
R.id.spreadview);
@Override
public final void onSensorChanged(SensorEvent event) {
proximityDebounceHandler.removeCallbacksAndMessages(null);
boolean isPn = isKeyguardProximityDetectionNecessary();
+int callState = mTelephonyManager.getCallState();
if (!isPn && callState != 1) {
disableProximityDetectionState();
return;
}
...
private void setProximityDetectedShowing(boolean show) {
int callState = mTelephonyManager.getCallState();
mProximityDetected = show;
if (show) {
onBackPressed();
mNotificationPanelView.setAlpha(0f);
mNotificationPanelView.setVisibility(View.INVISIBLE);
if (callState == 1) {
showDialog();
}else{
proximity_top.setText(R.string.proximity_top);
proximity_mode.setText(R.string.proximity_mode);
proximity_action.setText(R.string.proximity_action);
mProximityDetectedView.setVisibility(View.VISIBLE);
mProximityDetectedView.setAlpha(1f);
mProximityDetectedView.setBackgroundDrawable(mWallpaperManager.getDrawable());
}
} else {
mNotificationPanelView.setVisibility(View.VISIBLE);
mNotificationPanelView.setAlpha(1f);
if (callState == 1 && mPocketModeDialog != null) {
mPocketModeDialog.dismiss();
mPocketModeDialog = null;
}else{
mProximityDetectedView.setVisibility(View.INVISIBLE);
mProximityDetectedView.setAlpha(0f);
}
isDismissDialog = false;
}
}
private void showDialog(){
if (mPocketModeDialog != null) {
return;
}
mPocketModeDialog = new SystemUIDialog(mContext,R.style.PocketModeDialog_Fullscreen);
View view = View.inflate(mContext, R.layout.status_bar_proximity_detected, null);
view.setBackgroundDrawable(mWallpaperManager.getDrawable());
TextView top = view.findViewById(R.id.proximity_top);
TextView mode = view.findViewById(R.id.proximity_mode);
TextView action = view.findViewById(R.id.proximity_action);
top.setText(R.string.proximity_top);
mode.setText(R.string.proximity_mode);
action.setText(R.string.proximity_action);
mPocketModeDialog.setView(view);
Window window = mPocketModeDialog.getWindow();
if (window != null) {
WindowManager.LayoutParams params = window.getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
window.setAttributes(params);
}
// mPocketModeDialog.getWindow().setBackgroundDrawable(mWallpaperManager.getDrawable());
mPocketModeDialog.setShowForAllUsers(true);
mPocketModeDialog.show();
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
}
private void unregisterSensorListener() {
boolean flag = mKeyguardManager.inKeyguardRestrictedInputMode();
int callState = mTelephonyManager.getCallState();
int pocket_mode = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.DISABLE_POCKET_MODE, 0);
if (flag && callState == 1 && pocket_mode == 1 && !isDismissDialog) {
return;
}
...
private TelephonyManager mTelephonyManager;
protected KeyguardManager mKeyguardManager;
frameworks/base / packages/SystemUI/res/values/styles.xml
<declare-styleable name="SpreadView">
<!--中心圆颜色-->
<attr name="spread_center_color" format="color"/>
<!--中心圆半径-->
<attr name="spread_radius" format="integer"/>
<!--扩散圆颜色-->
<attr name="spread_spread_color" format="color"/>
<!--扩散间距-->
<attr name="spread_distance" format="integer"/>
<!--扩散最大半径-->
<attr name="spread_max_radius" format="integer"/>
<!--扩散延迟间隔-->
<attr name="spread_delay_milliseconds" format="integer"/>
</declare-styleable>
<style name="PocketModeDialog_Fullscreen">
<item name="android:windowFullscreen">true</item>
<item name="android:windowNoTitle">true</item>
</style>
frameworks/base / packages/SystemUI/res/values/strings.xml
<string name="proximity_top">Don’t cover the top of the screen</string>
<string name="proximity_mode">Pocket Mode</string>
<string name="proximity_action">Swipe left or right to force exit</string>