diff --git a/CleanSpec.mk b/CleanSpec.mk index e6801034cd97..f8424a2d7890 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -78,6 +78,7 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/symbols/system/lib/libhwui.so) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/lib/libhwui.so) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/os/storage/*) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/content/IClipboard.P) +$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/pocket/*) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/telephony/java/com/android/internal/telephony/ITelephonyRegistry.P) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android_stubs_current_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/docs/api-stubs*) diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index f77ddb1f83b4..a18c8f940db0 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -218,6 +218,8 @@ import android.permission.PermissionCheckerManager; import android.permission.PermissionControllerManager; import android.permission.PermissionManager; +import android.pocket.IPocketService; +import android.pocket.PocketManager; import android.print.IPrintManager; import android.print.PrintManager; import android.provider.E2eeContactKeysManager; @@ -1006,6 +1008,15 @@ public BiometricManager createService(ContextImpl ctx) } }); + registerService(Context.POCKET_SERVICE, PocketManager.class, + new CachedServiceFetcher() { + @Override + public PocketManager createService(ContextImpl ctx) { + IBinder binder = ServiceManager.getService(Context.POCKET_SERVICE); + IPocketService service = IPocketService.Stub.asInterface(binder); + return new PocketManager(ctx.getOuterContext(), service); + }}); + registerService(Context.TV_INTERACTIVE_APP_SERVICE, TvInteractiveAppManager.class, new CachedServiceFetcher() { @Override diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index aaa08203084f..c0a274ebcf10 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -6650,6 +6650,16 @@ public abstract boolean startInstrumentation(@NonNull ComponentName className, */ public static final String DC_DIM_SERVICE = "dc_dim"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.os.PocketManager} for accessing and listening to device pocket state. + * + * @hide + * @see #getSystemService + * @see android.os.PocketManager + */ + public static final String POCKET_SERVICE = "pocket"; + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. diff --git a/core/java/android/pocket/IPocketCallback.aidl b/core/java/android/pocket/IPocketCallback.aidl new file mode 100644 index 000000000000..53e5412f89be --- /dev/null +++ b/core/java/android/pocket/IPocketCallback.aidl @@ -0,0 +1,24 @@ +/** + * Copyright (C) 2016 The ParanoidAndroid 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 android.pocket; + +/** @hide */ +interface IPocketCallback { + + // notify when pocket state changes. + void onStateChanged(boolean isDeviceInPocket, int reason); + +} \ No newline at end of file diff --git a/core/java/android/pocket/IPocketService.aidl b/core/java/android/pocket/IPocketService.aidl new file mode 100644 index 000000000000..783465774207 --- /dev/null +++ b/core/java/android/pocket/IPocketService.aidl @@ -0,0 +1,43 @@ +/** + * Copyright (C) 2016 The ParanoidAndroid 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 android.pocket; + +import android.pocket.IPocketCallback; + +/** @hide */ +interface IPocketService { + + // add callback to get notified about pocket state. + void addCallback(IPocketCallback callback); + + // remove callback and stop getting notified about pocket state. + void removeCallback(IPocketCallback callback); + + // notify pocket service about intercative state changed. + // @see com.android.policy.PhoneWindowManager + void onInteractiveChanged(boolean interactive); + + // external processes can request changing listening state. + void setListeningExternal(boolean listen); + + // check if device is in pocket. + boolean isDeviceInPocket(); + + // Custom methods + void setPocketLockVisible(boolean visible); + boolean isPocketLockVisible(); + +} \ No newline at end of file diff --git a/core/java/android/pocket/PocketConstants.java b/core/java/android/pocket/PocketConstants.java new file mode 100644 index 000000000000..865aeb66688c --- /dev/null +++ b/core/java/android/pocket/PocketConstants.java @@ -0,0 +1,28 @@ +package android.pocket; + +import android.content.res.Resources; + +/** + * This class contains global pocket setup constants. + * @author Carlo Savignano + * @hide + */ + +public class PocketConstants { + + public static final boolean DEBUG = false; + public static final boolean DEBUG_SPEW = false; + + /** + * Whether to use proximity sensor to evaluate pocket state. + */ + public static final boolean ENABLE_PROXIMITY_JUDGE = true; + + /** + * Whether to use light sensor to evaluate pocket state. + */ + public static final boolean ENABLE_LIGHT_JUDGE = Resources.getSystem().getBoolean( + com.android.internal.R.bool.config_pocketModeLightSensorSupported); + + +} diff --git a/core/java/android/pocket/PocketManager.java b/core/java/android/pocket/PocketManager.java new file mode 100644 index 000000000000..52383f0768fc --- /dev/null +++ b/core/java/android/pocket/PocketManager.java @@ -0,0 +1,237 @@ +/** + * Copyright (C) 2016 The ParanoidAndroid 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 android.pocket; + +import android.content.Context; +import android.os.Handler; +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.SystemClock; +import android.telecom.TelecomManager; +import android.text.format.DateUtils; +import android.util.Log; +import android.util.Slog; + +/** + * A class that coordinates listening for pocket state. + *

+ * Use {@link android.content.Context#getSystemService(java.lang.String)} + * with argument {@link android.content.Context#POCKET_SERVICE} to get + * an instance of this class. + * + * Usage: import and create a final {@link IPocketCallback.Stub()} and implement your logic in + * {@link IPocketCallback#onStateChanged(boolean, int)}. Then add your callback to the pocket manager + * + * // define a final callback + * private final IPocketCallback mCallback = new IPocketCallback.Stub() { + * + * @Override + * public void onStateChanged(boolean isDeviceInPocket, int reason) { + * // Your method to handle logic outside of this callback, ideally with a handler + * // posting on UI Thread for view hierarchy operations or with its own background thread. + * handlePocketStateChanged(isDeviceInPocket, reason); + * } + * + * } + * + * // add callback to pocket manager + * private void addCallback() { + * PocketManager manager = (PocketManager) context.getSystemService(Context.POCKET_SERVICE); + * manager.addCallback(mCallback); + * } + * + * @author Carlo Savignano + * @hide + */ +public class PocketManager { + + private static final String TAG = PocketManager.class.getSimpleName(); + static final boolean DEBUG = false; + + /** + * Whether {@link IPocketCallback#onStateChanged(boolean, int)} + * was fired because of the sensor. + * @see PocketService#handleDispatchCallbacks() + */ + public static final int REASON_SENSOR = 0; + + /** + * Whether {@link IPocketCallback#onStateChanged(boolean, int)} + * was fired because of an error while accessing service. + * @see #addCallback(IPocketCallback) + * @see #removeCallback(IPocketCallback) + */ + public static final int REASON_ERROR = 1; + + /** + * Whether {@link IPocketCallback#onStateChanged(boolean, int)} + * was fired because of a needed reset. + * @see PocketService#binderDied() + */ + public static final int REASON_RESET = 2; + + private Context mContext; + private IPocketService mService; + private PowerManager mPowerManager; + private TelecomManager mTelecomManager; + private Handler mHandler; + private boolean mPocketViewTimerActive; + + public PocketManager(Context context, IPocketService service) { + mContext = context; + mService = service; + if (mService == null) { + Slog.v(TAG, "PocketService was null"); + } + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mTelecomManager = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE); + mHandler = new Handler(); + } + + /** + * Add pocket state callback. + * @see PocketService#handleRemoveCallback(IPocketCallback) + */ + public void addCallback(final IPocketCallback callback) { + if (mService != null) try { + mService.addCallback(callback); + } catch (RemoteException e1) { + Log.w(TAG, "Remote exception in addCallback: ", e1); + if (callback != null){ + try { + callback.onStateChanged(false, REASON_ERROR); + } catch (RemoteException e2) { + Log.w(TAG, "Remote exception in callback.onPocketStateChanged: ", e2); + } + } + } + } + + /** + * Remove pocket state callback. + * @see PocketService#handleAddCallback(IPocketCallback) + */ + public void removeCallback(final IPocketCallback callback) { + if (mService != null) try { + mService.removeCallback(callback); + } catch (RemoteException e1) { + Log.w(TAG, "Remote exception in removeCallback: ", e1); + if (callback != null){ + try { + callback.onStateChanged(false, REASON_ERROR); + } catch (RemoteException e2) { + Log.w(TAG, "Remote exception in callback.onPocketStateChanged: ", e2); + } + } + } + } + + /** + * Notify service about device interactive state changed. + * {@link PhoneWindowManager#startedWakingUp()} + * {@link PhoneWindowManager#startedGoingToSleep(int)} + */ + public void onInteractiveChanged(boolean interactive) { + boolean isPocketViewShowing = (interactive && isDeviceInPocket()); + synchronized (mPocketLockTimeout) { + if (mPocketViewTimerActive != isPocketViewShowing) { + if (isPocketViewShowing) { + if (DEBUG) Log.v(TAG, "Setting pocket timer"); + mHandler.removeCallbacks(mPocketLockTimeout); // remove any pending requests + mHandler.postDelayed(mPocketLockTimeout, 3 * DateUtils.SECOND_IN_MILLIS); + mPocketViewTimerActive = true; + } else { + if (DEBUG) Log.v(TAG, "Clearing pocket timer"); + mHandler.removeCallbacks(mPocketLockTimeout); + mPocketViewTimerActive = false; + } + } + } + if (mService != null) try { + mService.onInteractiveChanged(interactive); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in addCallback: ", e); + } + } + + /** + * Request listening state change by, but not limited to, external process. + * @see PocketService#handleSetListeningExternal(boolean) + */ + public void setListeningExternal(boolean listen) { + if (mService != null) try { + mService.setListeningExternal(listen); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in setListeningExternal: ", e); + } + // Clear timeout when user hides pocket lock with long press power. + if (mPocketViewTimerActive && !listen) { + if (DEBUG) Log.v(TAG, "Clearing pocket timer due to override"); + mHandler.removeCallbacks(mPocketLockTimeout); + mPocketViewTimerActive = false; + } + } + + /** + * Return whether device is in pocket. + * @see PocketService#isDeviceInPocket() + * @return + */ + public boolean isDeviceInPocket() { + if (mService != null) try { + return mService.isDeviceInPocket(); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in isDeviceInPocket: ", e); + } + return false; + } + + class PocketLockTimeout implements Runnable { + @Override + public void run() { + if (!mTelecomManager.isInCall()) + mPowerManager.goToSleep(SystemClock.uptimeMillis()); + mPocketViewTimerActive = false; + } + } + + /** Custom methods **/ + + public void setPocketLockVisible(boolean visible) { + if (!visible){ + if (DEBUG) Log.v(TAG, "Clearing pocket timer"); + mHandler.removeCallbacks(mPocketLockTimeout); + mPocketViewTimerActive = false; + } + if (mService != null) try { + mService.setPocketLockVisible(visible); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in setPocketLockVisible: ", e); + } + } + + public boolean isPocketLockVisible() { + if (mService != null) try { + return mService.isPocketLockVisible(); + } catch (RemoteException e) { + Log.w(TAG, "Remote exception in isPocketLockVisible: ", e); + } + return false; + } + + private PocketLockTimeout mPocketLockTimeout = new PocketLockTimeout(); + +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index cbe8f9aa277a..65a1eb73cc10 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6337,6 +6337,15 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean */ public static final String GESTURE_DOUBLE_TAP_SLEEP = "gesture_double_tap_sleep"; + /** + * Whether allowing pocket service to register sensors and dispatch informations. + * 0 = disabled + * 1 = enabled + * @author Carlo Savignano + * @hide + */ + public static final String POCKET_JUDGE = "pocket_judge"; + /** * Keys we no longer back up under the current schema, but want to continue to * process when restoring historical backup datasets. @@ -6472,6 +6481,7 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean PRIVATE_SETTINGS.add(SCREEN_FLASH_NOTIFICATION_COLOR); PRIVATE_SETTINGS.add(DEFAULT_DEVICE_FONT_SCALE); PRIVATE_SETTINGS.add(CALL_CONNECTED_TONE_ENABLED); + PRIVATE_SETTINGS.add(POCKET_JUDGE); } /** diff --git a/core/res/res/drawable/pocket_mode_illustration.xml b/core/res/res/drawable/pocket_mode_illustration.xml new file mode 100644 index 000000000000..b263d8a37f3b --- /dev/null +++ b/core/res/res/drawable/pocket_mode_illustration.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/core/res/res/layout/pocket_lock_view.xml b/core/res/res/layout/pocket_lock_view.xml new file mode 100644 index 000000000000..41a3b915954d --- /dev/null +++ b/core/res/res/layout/pocket_lock_view.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/res/res/values/aospa_config.xml b/core/res/res/values/aospa_config.xml index b66eeb762d36..f29e4634426d 100644 --- a/core/res/res/values/aospa_config.xml +++ b/core/res/res/values/aospa_config.xml @@ -80,4 +80,16 @@ will be charged between (limit - val) and limit. --> 10 + + true + true + + + + 1.0 + + + + diff --git a/core/res/res/values/aospa_dimens.xml b/core/res/res/values/aospa_dimens.xml new file mode 100644 index 000000000000..55540afa525c --- /dev/null +++ b/core/res/res/values/aospa_dimens.xml @@ -0,0 +1,32 @@ + + + + + + 175dp + 350dp + 56dp + 24sp + 56dp + 24dp + 56dp + 16sp + 8dp + 14sp + 8dp + + diff --git a/core/res/res/values/aospa_strings.xml b/core/res/res/values/aospa_strings.xml index 1027c4d10f07..904d2d03c03a 100644 --- a/core/res/res/values/aospa_strings.xml +++ b/core/res/res/values/aospa_strings.xml @@ -29,4 +29,11 @@ Battery will be fully charged at %1$s Battery is charged + + Pocket mode is active + Illustration of phone in pocket + To use your phone: + 1. Ensure the green area (proximity sensor) shown above is unobstructed and free from dirt or dust. + 2. Press and hold the power button to exit pocket mode. + diff --git a/core/res/res/values/aospa_styles.xml b/core/res/res/values/aospa_styles.xml new file mode 100644 index 000000000000..a7968fa821bb --- /dev/null +++ b/core/res/res/values/aospa_styles.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/core/res/res/values/aospa_symbols.xml b/core/res/res/values/aospa_symbols.xml index b953b72fadb3..1217cf01e9d7 100644 --- a/core/res/res/values/aospa_symbols.xml +++ b/core/res/res/values/aospa_symbols.xml @@ -52,4 +52,16 @@ + + + + + + + + + + + + diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 09c9dd19c390..5131124927c3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -88,6 +88,8 @@ import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; +import android.pocket.IPocketCallback; +import android.pocket.PocketManager; import android.provider.Settings; import android.service.dreams.IDreamManager; import android.telephony.CarrierConfigManager; @@ -220,6 +222,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_SERVICE_PROVIDERS_UPDATED = 347; private static final int MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED = 348; + // Additional messages should be 600+ + private static final int MSG_POCKET_STATE_CHANGED = 600; + /** Biometric authentication state: Not listening. */ @VisibleForTesting protected static final int BIOMETRIC_STATE_STOPPED = 0; @@ -418,6 +423,27 @@ protected Handler getHandler() { private final Handler mHandler; + private PocketManager mPocketManager; + private boolean mIsDeviceInPocket; + private final IPocketCallback mPocketCallback = new IPocketCallback.Stub() { + @Override + public void onStateChanged(boolean isDeviceInPocket, int reason) { + boolean wasInPocket = mIsDeviceInPocket; + if (reason == PocketManager.REASON_SENSOR) { + mIsDeviceInPocket = isDeviceInPocket; + } else { + mIsDeviceInPocket = false; + } + if (wasInPocket != mIsDeviceInPocket) { + mHandler.sendEmptyMessage(MSG_POCKET_STATE_CHANGED); + } + } + }; + + public boolean isPocketLockVisible(){ + return mPocketManager.isPocketLockVisible(); + } + private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback = new IBiometricEnabledOnKeyguardCallback.Stub() { @Override @@ -2196,6 +2222,11 @@ protected KeyguardUpdateMonitor( mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null); mIsSystemUser = mUserManager.isSystemUser(); + mPocketManager = (PocketManager) context.getSystemService(Context.POCKET_SERVICE); + if (mPocketManager != null) { + mPocketManager.addCallback(mPocketCallback); + } + mHandler = new Handler(mainLooper) { @Override public void handleMessage(Message msg) { @@ -2303,6 +2334,9 @@ public void handleMessage(Message msg) { case MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED: notifyAboutEnrollmentChange(msg.arg1); break; + case MSG_POCKET_STATE_CHANGED: + updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE); + break; default: super.handleMessage(msg); break; @@ -2845,7 +2879,8 @@ protected boolean shouldListenForFingerprint(boolean isUdfps) { boolean shouldListen = shouldListenKeyguardState && shouldListenUserState - && shouldListenBouncerState && shouldListenUdfpsState && !mBiometricPromptShowing; + && shouldListenBouncerState && shouldListenUdfpsState && !mBiometricPromptShowing + && !mIsDeviceInPocket; logListenerModelData( new KeyguardFingerprintListenModel( System.currentTimeMillis(), diff --git a/services/core/java/com/android/server/pocket/PocketBridgeService.java b/services/core/java/com/android/server/pocket/PocketBridgeService.java new file mode 100644 index 000000000000..5fc5e2721cc4 --- /dev/null +++ b/services/core/java/com/android/server/pocket/PocketBridgeService.java @@ -0,0 +1,184 @@ +/** + * Copyright (C) 2017 The ParanoidAndroid 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.server.pocket; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; + +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.UserHandle; +import android.pocket.IPocketCallback; +import android.pocket.PocketManager; +import android.provider.Settings.System; +import android.util.Slog; +import com.android.internal.util.FastPrintWriter; +import com.android.server.SystemService; + +import static android.provider.Settings.System.POCKET_JUDGE; + +/** + * This service communicates pocket state to the pocket judge kernel driver. + * It maintains the pocket state by binding to the pocket service. + * + * @author Chris Lahaye + * @hide + */ +public class PocketBridgeService extends SystemService { + + private static final String TAG = PocketBridgeService.class.getSimpleName(); + private static final int MSG_POCKET_STATE_CHANGED = 1; + + private Context mContext; + private boolean mEnabled; + private PocketBridgeHandler mHandler; + private PocketBridgeObserver mObserver; + + private PocketManager mPocketManager; + private boolean mIsDeviceInPocket; + private final IPocketCallback mPocketCallback = new IPocketCallback.Stub() { + @Override + public void onStateChanged(boolean isDeviceInPocket, int reason) { + boolean changed = false; + if (reason == PocketManager.REASON_SENSOR) { + if (isDeviceInPocket != mIsDeviceInPocket) { + mIsDeviceInPocket = isDeviceInPocket; + changed = true; + } + } else { + changed = isDeviceInPocket != mIsDeviceInPocket; + mIsDeviceInPocket = false; + } + if (changed) { + mHandler.sendEmptyMessage(MSG_POCKET_STATE_CHANGED); + } + } + }; + + // Custom methods + private boolean mSupportedByDevice; + + public PocketBridgeService(Context context) { + super(context); + mContext = context; + HandlerThread handlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); + handlerThread.start(); + mHandler = new PocketBridgeHandler(handlerThread.getLooper()); + mPocketManager = (PocketManager) + context.getSystemService(Context.POCKET_SERVICE); + mSupportedByDevice = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_pocketModeSupported); + mObserver = new PocketBridgeObserver(mHandler); + if (mSupportedByDevice){ + mObserver.onChange(true); + mObserver.register(); + } + } + + @Override + public void onStart() { + } + + private void setEnabled(boolean enabled) { + if (enabled != mEnabled) { + mEnabled = enabled; + update(); + } + } + + private void update() { + if (!mSupportedByDevice || mPocketManager == null) return; + + if (mEnabled) { + mPocketManager.addCallback(mPocketCallback); + } else { + mPocketManager.removeCallback(mPocketCallback); + } + } + + private class PocketBridgeHandler extends Handler { + + private FileOutputStream mFileOutputStream; + private FastPrintWriter mPrintWriter; + + public PocketBridgeHandler(Looper looper) { + super(looper); + + try { + mFileOutputStream = new FileOutputStream( + mContext.getResources().getString( + com.android.internal.R.string.config_pocketBridgeSysfsInpocket) + ); + mPrintWriter = new FastPrintWriter(mFileOutputStream, true, 128); + } + catch(FileNotFoundException e) { + Slog.w(TAG, "Pocket bridge error occured", e); + setEnabled(false); + } + } + + @Override + public void handleMessage(android.os.Message msg) { + if (msg.what != MSG_POCKET_STATE_CHANGED) { + Slog.w(TAG, "Unknown message:" + msg.what); + return; + } + + if (mPrintWriter != null) { + mPrintWriter.println(mIsDeviceInPocket ? 1 : 0); + } + } + + } + + private class PocketBridgeObserver extends ContentObserver { + + private boolean mRegistered; + + public PocketBridgeObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + final boolean enabled = System.getIntForUser(mContext.getContentResolver(), + POCKET_JUDGE, 0 /* default */, UserHandle.USER_CURRENT) != 0; + setEnabled(enabled); + } + + public void register() { + if (!mRegistered) { + mContext.getContentResolver().registerContentObserver( + System.getUriFor(POCKET_JUDGE), true, this); + mRegistered = true; + } + } + + public void unregister() { + if (mRegistered) { + mContext.getContentResolver().unregisterContentObserver(this); + mRegistered = false; + } + } + + } + +} diff --git a/services/core/java/com/android/server/pocket/PocketService.java b/services/core/java/com/android/server/pocket/PocketService.java new file mode 100644 index 000000000000..09c2e41e4cbd --- /dev/null +++ b/services/core/java/com/android/server/pocket/PocketService.java @@ -0,0 +1,907 @@ +/** + * Copyright (C) 2016 The ParanoidAndroid 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.server.pocket; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Binder; +import android.os.DeadObjectException; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.pocket.IPocketService; +import android.pocket.IPocketCallback; +import android.pocket.PocketConstants; +import android.pocket.PocketManager; +import android.provider.Settings.System; +import android.text.TextUtils; +import android.util.Log; +import android.util.Slog; + +import com.android.server.SystemService; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + +import static android.provider.Settings.System.POCKET_JUDGE; + +/** + * A service to manage multiple clients that want to listen for pocket state. + * The service is responsible for maintaining a list of clients and dispatching all + * pocket -related information. + * + * @author Carlo Savignano + * @hide + */ +public class PocketService extends SystemService implements IBinder.DeathRecipient { + + private static final String TAG = PocketService.class.getSimpleName(); + private static final boolean DEBUG = PocketConstants.DEBUG; + + /** + * Wheater we don't have yet a valid vendor sensor event or pocket service not running. + */ + private static final int VENDOR_SENSOR_UNKNOWN = 0; + + /** + * Vendor sensor has been registered, onSensorChanged() has been called and we have a + * valid event value from Vendor pocket sensor. + */ + private static final int VENDOR_SENSOR_IN_POCKET = 1; + + /** + * The rate proximity sensor events are delivered at. + */ + private static final int PROXIMITY_SENSOR_DELAY = 400000; + + /** + * Wheater we don't have yet a valid proximity sensor event or pocket service not running. + */ + private static final int PROXIMITY_UNKNOWN = 0; + + /** + * Proximity sensor has been registered, onSensorChanged() has been called and we have a + * valid event value which determined proximity sensor is covered. + */ + private static final int PROXIMITY_POSITIVE = 1; + + /** + * Proximity sensor has been registered, onSensorChanged() has been called and we have a + * valid event value which determined proximity sensor is not covered. + */ + private static final int PROXIMITY_NEGATIVE = 2; + + /** + * The rate light sensor events are delivered at. + */ + private static final int LIGHT_SENSOR_DELAY = 400000; + + /** + * Wheater we don't have yet a valid light sensor event or pocket service not running. + */ + private static final int LIGHT_UNKNOWN = 0; + + /** + * Light sensor has been registered, onSensorChanged() has been called and we have a + * valid event value which determined available light is in pocket range. + */ + private static final int LIGHT_POCKET = 1; + + /** + * Light sensor has been registered, onSensorChanged() has been called and we have a + * valid event value which determined available light is outside pocket range. + */ + private static final int LIGHT_AMBIENT = 2; + + /** + * Light sensor maximum value registered in pocket with up to semi-transparent fabric. + */ + private static final float POCKET_LIGHT_MAX_THRESHOLD = 3.0f; + + private final ArrayList mCallbacks= new ArrayList<>(); + + private Context mContext; + private boolean mEnabled; + private boolean mSystemReady; + private boolean mSystemBooted; + private boolean mInteractive; + private boolean mPending; + private PocketHandler mHandler; + private PocketObserver mObserver; + private SensorManager mSensorManager; + + // proximity + private int mProximityState = PROXIMITY_UNKNOWN; + private int mLastProximityState = PROXIMITY_UNKNOWN; + private float mProximityMaxRange; + private boolean mProximityRegistered; + private Sensor mProximitySensor; + + // light + private int mLightState = LIGHT_UNKNOWN; + private int mLastLightState = LIGHT_UNKNOWN; + private float mLightMaxRange; + private boolean mLightRegistered; + private Sensor mLightSensor; + + // vendor sensor + private int mVendorSensorState = VENDOR_SENSOR_UNKNOWN; + private int mLastVendorSensorState = VENDOR_SENSOR_UNKNOWN; + private String mVendorPocketSensor; + private float mVendorPocketSensorValue; + private boolean mVendorSensorRegistered; + private Sensor mVendorSensor; + + // Custom methods + private boolean mPocketLockVisible; + private boolean mSupportedByDevice; + + public PocketService(Context context) { + super(context); + mContext = context; + HandlerThread handlerThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND); + handlerThread.start(); + mHandler = new PocketHandler(handlerThread.getLooper()); + mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); + mVendorPocketSensor = mContext.getResources().getString( + com.android.internal.R.string.config_pocketJudgeVendorSensorName); + mVendorPocketSensorValue = mContext.getResources().getFloat( + com.android.internal.R.dimen.config_pocketJudgeVendorSensorValue); + mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + if (mProximitySensor != null) { + mProximityMaxRange = mProximitySensor.getMaximumRange(); + } + mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + if (mLightSensor != null) { + mLightMaxRange = mLightSensor.getMaximumRange(); + } + mVendorSensor = getSensor(mSensorManager, mVendorPocketSensor); + mSupportedByDevice = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_pocketModeSupported); + mObserver = new PocketObserver(mHandler); + if (mSupportedByDevice){ + mObserver.onChange(true); + mObserver.register(); + } + } + + private class PocketObserver extends ContentObserver { + + private boolean mRegistered; + + public PocketObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + final boolean enabled = System.getIntForUser(mContext.getContentResolver(), + POCKET_JUDGE, 0 /* default */, UserHandle.USER_CURRENT) != 0; + setEnabled(enabled); + } + + public void register() { + if (!mRegistered) { + mContext.getContentResolver().registerContentObserver( + System.getUriFor(POCKET_JUDGE), true, this); + mRegistered = true; + } + } + + public void unregister() { + if (mRegistered) { + mContext.getContentResolver().unregisterContentObserver(this); + mRegistered = false; + } + } + + } + + private class PocketHandler extends Handler { + + public static final int MSG_SYSTEM_READY = 0; + public static final int MSG_SYSTEM_BOOTED = 1; + public static final int MSG_DISPATCH_CALLBACKS = 2; + public static final int MSG_ADD_CALLBACK = 3; + public static final int MSG_REMOVE_CALLBACK = 4; + public static final int MSG_INTERACTIVE_CHANGED = 5; + public static final int MSG_SENSOR_EVENT_PROXIMITY = 6; + public static final int MSG_SENSOR_EVENT_LIGHT = 7; + public static final int MSG_UNREGISTER_TIMEOUT = 8; + public static final int MSG_SET_LISTEN_EXTERNAL = 9; + public static final int MSG_SET_POCKET_LOCK_VISIBLE = 10; + public static final int MSG_SENSOR_EVENT_VENDOR = 11; + + public PocketHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(android.os.Message msg) { + switch (msg.what) { + case MSG_SYSTEM_READY: + handleSystemReady(); + break; + case MSG_SYSTEM_BOOTED: + handleSystemBooted(); + break; + case MSG_DISPATCH_CALLBACKS: + handleDispatchCallbacks(); + break; + case MSG_ADD_CALLBACK: + handleAddCallback((IPocketCallback) msg.obj); + break; + case MSG_REMOVE_CALLBACK: + handleRemoveCallback((IPocketCallback) msg.obj); + break; + case MSG_INTERACTIVE_CHANGED: + handleInteractiveChanged(msg.arg1 != 0); + break; + case MSG_SENSOR_EVENT_PROXIMITY: + handleProximitySensorEvent((SensorEvent) msg.obj); + break; + case MSG_SENSOR_EVENT_LIGHT: + handleLightSensorEvent((SensorEvent) msg.obj); + break; + case MSG_SENSOR_EVENT_VENDOR: + handleVendorSensorEvent((SensorEvent) msg.obj); + break; + case MSG_UNREGISTER_TIMEOUT: + handleUnregisterTimeout(); + break; + case MSG_SET_LISTEN_EXTERNAL: + handleSetListeningExternal(msg.arg1 != 0); + break; + case MSG_SET_POCKET_LOCK_VISIBLE: + handleSetPocketLockVisible(msg.arg1 != 0); + break; + default: + Slog.w(TAG, "Unknown message:" + msg.what); + } + } + } + + @Override + public void onBootPhase(int phase) { + switch(phase) { + case PHASE_SYSTEM_SERVICES_READY: + mHandler.sendEmptyMessage(PocketHandler.MSG_SYSTEM_READY); + break; + case PHASE_BOOT_COMPLETED: + mHandler.sendEmptyMessage(PocketHandler.MSG_SYSTEM_BOOTED); + break; + default: + Slog.w(TAG, "Un-handled boot phase:" + phase); + break; + } + } + + @Override + public void onStart() { + publishBinderService(Context.POCKET_SERVICE, new PocketServiceWrapper()); + } + + @Override + public void binderDied() { + synchronized (mCallbacks) { + mProximityState = PROXIMITY_UNKNOWN; + int callbacksSize = mCallbacks.size(); + for (int i = callbacksSize - 1; i >= 0; i--) { + if (mCallbacks.get(i) != null) { + try { + mCallbacks.get(i).onStateChanged(false, PocketManager.REASON_RESET); + } catch (DeadObjectException e) { + Slog.w(TAG, "Death object while invoking sendPocketState: ", e); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to invoke sendPocketState: ", e); + } + } + } + mCallbacks.clear(); + } + unregisterSensorListeners(); + mObserver.unregister(); + } + + private final class PocketServiceWrapper extends IPocketService.Stub { + + @Override // Binder call + public void addCallback(final IPocketCallback callback) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_ADD_CALLBACK; + msg.obj = callback; + mHandler.sendMessage(msg); + } + + @Override // Binder call + public void removeCallback(final IPocketCallback callback) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_REMOVE_CALLBACK; + msg.obj = callback; + mHandler.sendMessage(msg); + } + + @Override // Binder call + public void onInteractiveChanged(final boolean interactive) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_INTERACTIVE_CHANGED; + msg.arg1 = interactive ? 1 : 0; + mHandler.sendMessage(msg); + } + + @Override // Binder call + public void setListeningExternal(final boolean listen) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_SET_LISTEN_EXTERNAL; + msg.arg1 = listen ? 1 : 0; + mHandler.sendMessage(msg); + } + + @Override // Binder call + public boolean isDeviceInPocket() { + final long ident = Binder.clearCallingIdentity(); + try { + if (!mSystemReady || !mSystemBooted) { + return false; + } + return PocketService.this.isDeviceInPocket(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + public void setPocketLockVisible(final boolean visible) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_SET_POCKET_LOCK_VISIBLE; + msg.arg1 = visible ? 1 : 0; + mHandler.sendMessage(msg); + } + + @Override // Binder call + public boolean isPocketLockVisible() { + final long ident = Binder.clearCallingIdentity(); + try { + if (!mSystemReady || !mSystemBooted) { + return false; + } + return PocketService.this.isPocketLockVisible(); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump Pocket from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + final long ident = Binder.clearCallingIdentity(); + try { + dumpInternal(pw); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + } + + private final SensorEventListener mProximityListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_SENSOR_EVENT_PROXIMITY; + msg.obj = sensorEvent; + mHandler.sendMessage(msg); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int i) { } + }; + + private final SensorEventListener mLightListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_SENSOR_EVENT_LIGHT; + msg.obj = sensorEvent; + mHandler.sendMessage(msg); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int i) { } + }; + + private final SensorEventListener mVendorSensorListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_SENSOR_EVENT_VENDOR; + msg.obj = sensorEvent; + mHandler.sendMessage(msg); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int i) { } + }; + + private boolean isDeviceInPocket() { + if (!mSupportedByDevice){ + return false; + } + + if (mVendorSensorState != VENDOR_SENSOR_UNKNOWN) { + return mVendorSensorState == VENDOR_SENSOR_IN_POCKET; + } + + if (mLightState != LIGHT_UNKNOWN) { + return mProximityState == PROXIMITY_POSITIVE + && mLightState == LIGHT_POCKET; + } + return mProximityState == PROXIMITY_POSITIVE; + } + + private void setEnabled(boolean enabled) { + if (!mSupportedByDevice){ + return; + } + if (enabled != mEnabled) { + mEnabled = enabled; + mHandler.removeCallbacksAndMessages(null); + update(); + } + } + + private void update() { + if (!mSupportedByDevice){ + return; + } + if (!mEnabled || mInteractive) { + if (mEnabled && isDeviceInPocket()) { + // if device is judged to be in pocket while switching + // to interactive state, we need to keep monitoring. + return; + } + unregisterSensorListeners(); + } else { + mHandler.removeMessages(PocketHandler.MSG_UNREGISTER_TIMEOUT); + registerSensorListeners(); + } + } + + private void registerSensorListeners() { + if (!mSupportedByDevice){ + return; + } + startListeningForVendorSensor(); + startListeningForProximity(); + startListeningForLight(); + } + + private void unregisterSensorListeners() { + if (!mSupportedByDevice){ + return; + } + stopListeningForVendorSensor(); + stopListeningForProximity(); + stopListeningForLight(); + } + + private void startListeningForVendorSensor() { + if (DEBUG) { + Log.d(TAG, "startListeningForVendorSensor()"); + } + + if (mVendorSensor == null) { + Log.d(TAG, "Cannot detect Vendor pocket sensor, sensor is NULL"); + return; + } + + if (!mVendorSensorRegistered) { + mSensorManager.registerListener(mVendorSensorListener, mVendorSensor, + SensorManager.SENSOR_DELAY_NORMAL, mHandler); + mVendorSensorRegistered = true; + } + } + + private void stopListeningForVendorSensor() { + if (DEBUG) { + Log.d(TAG, "stopListeningForVendorSensor()"); + } + + if (mVendorSensorRegistered) { + mVendorSensorState = mLastVendorSensorState = VENDOR_SENSOR_UNKNOWN; + mSensorManager.unregisterListener(mVendorSensorListener); + mVendorSensorRegistered = false; + } + } + + private void startListeningForProximity() { + if (mVendorSensor != null) { + return; + } + + if (DEBUG) { + Log.d(TAG, "startListeningForProximity()"); + } + + if (!PocketConstants.ENABLE_PROXIMITY_JUDGE) { + return; + } + + if (mProximitySensor == null) { + Log.d(TAG, "Cannot detect proximity sensor, sensor is NULL"); + return; + } + + if (!mProximityRegistered) { + mSensorManager.registerListener(mProximityListener, mProximitySensor, + PROXIMITY_SENSOR_DELAY, mHandler); + mProximityRegistered = true; + } + } + + private void stopListeningForProximity() { + if (DEBUG) { + Log.d(TAG, "startListeningForProximity()"); + } + + if (mProximityRegistered) { + mLastProximityState = mProximityState = PROXIMITY_UNKNOWN; + mSensorManager.unregisterListener(mProximityListener); + mProximityRegistered = false; + } + } + + private void startListeningForLight() { + if (mVendorSensor != null) { + return; + } + + if (DEBUG) { + Log.d(TAG, "startListeningForLight()"); + } + + if (!PocketConstants.ENABLE_LIGHT_JUDGE) { + return; + } + + if (mLightSensor == null) { + Log.d(TAG, "Cannot detect light sensor, sensor is NULL"); + return; + } + + if (!mLightRegistered) { + mSensorManager.registerListener(mLightListener, mLightSensor, + LIGHT_SENSOR_DELAY, mHandler); + mLightRegistered = true; + } + } + + private void stopListeningForLight() { + if (DEBUG) { + Log.d(TAG, "stopListeningForLight()"); + } + + if (mLightRegistered) { + mLightState = mLastLightState = LIGHT_UNKNOWN; + mSensorManager.unregisterListener(mLightListener); + mLightRegistered = false; + } + } + + private void handleSystemReady() { + if (DEBUG) { + Log.d(TAG, "onBootPhase(): PHASE_SYSTEM_SERVICES_READY"); + Log.d(TAG, "onBootPhase(): VENDOR_SENSOR: " + mVendorPocketSensor); + } + mSystemReady = true; + + if (mPending) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_INTERACTIVE_CHANGED; + msg.arg1 = mInteractive ? 1 : 0; + mHandler.sendMessage(msg); + mPending = false; + } + } + + private void handleSystemBooted() { + if (DEBUG) { + Log.d(TAG, "onBootPhase(): PHASE_BOOT_COMPLETED"); + } + mSystemBooted = true; + if (mPending) { + final Message msg = new Message(); + msg.what = PocketHandler.MSG_INTERACTIVE_CHANGED; + msg.arg1 = mInteractive ? 1 : 0; + mHandler.sendMessage(msg); + mPending = false; + } + } + + private void handleDispatchCallbacks() { + synchronized (mCallbacks) { + final int N = mCallbacks.size(); + boolean cleanup = false; + for (int i = 0; i < N; i++) { + final IPocketCallback callback = mCallbacks.get(i); + try { + if (callback != null) { + callback.onStateChanged(isDeviceInPocket(), PocketManager.REASON_SENSOR); + } else { + cleanup = true; + } + } catch (RemoteException e) { + cleanup = true; + } + } + if (cleanup) { + cleanUpCallbacksLocked(null); + } + } + } + + private void cleanUpCallbacksLocked(IPocketCallback callback) { + synchronized (mCallbacks) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + IPocketCallback found = mCallbacks.get(i); + if (found == null || found == callback) { + mCallbacks.remove(i); + } + } + } + } + + private void handleSetPocketLockVisible(boolean visible) { + mPocketLockVisible = visible; + } + + private boolean isPocketLockVisible() { + return mPocketLockVisible; + } + + private void handleSetListeningExternal(boolean listen) { + if (listen) { + // should prevent external processes to register while interactive, + // while they are allowed to stop listening in any case as for example + // coming pocket lock will need to. + if (!mInteractive) { + registerSensorListeners(); + } + } else { + mHandler.removeCallbacksAndMessages(null); + unregisterSensorListeners(); + } + dispatchCallbacks(); + } + + private void handleAddCallback(IPocketCallback callback) { + synchronized (mCallbacks) { + if (!mCallbacks.contains(callback)) { + mCallbacks.add(callback); + } + } + } + + private void handleRemoveCallback(IPocketCallback callback) { + synchronized (mCallbacks) { + if (mCallbacks.contains(callback)) { + mCallbacks.remove(callback); + } + } + } + + private void handleInteractiveChanged(boolean interactive) { + // always update interactive state. + mInteractive = interactive; + + if (mPending) { + // working on it, waiting for proper system conditions. + return; + } else if (!mPending && (!mSystemBooted || !mSystemReady)) { + // we ain't ready, postpone till system is both booted AND ready. + mPending = true; + return; + } + + update(); + } + + private void handleVendorSensorEvent(SensorEvent sensorEvent) { + final boolean isDeviceInPocket = isDeviceInPocket(); + + mLastVendorSensorState = mVendorSensorState; + + if (DEBUG) { + final String sensorEventToString = sensorEvent != null ? sensorEvent.toString() : "NULL"; + Log.d(TAG, "VENDOR_SENSOR: onSensorChanged(), sensorEvent =" + sensorEventToString); + } + + try { + if (sensorEvent == null) { + if (DEBUG) Log.d(TAG, "Event is null!"); + mVendorSensorState = VENDOR_SENSOR_UNKNOWN; + } else if (sensorEvent.values == null || sensorEvent.values.length == 0) { + if (DEBUG) Log.d(TAG, "Event has no values! event.values null ? " + (sensorEvent.values == null)); + mVendorSensorState = VENDOR_SENSOR_UNKNOWN; + } else { + final boolean isVendorPocket = sensorEvent.values[0] == mVendorPocketSensorValue; + if (DEBUG) { + final long time = SystemClock.uptimeMillis(); + Log.d(TAG, "Event: time=" + time + ", value=" + sensorEvent.values[0] + + ", isInPocket=" + isVendorPocket); + } + mVendorSensorState = isVendorPocket ? VENDOR_SENSOR_IN_POCKET : VENDOR_SENSOR_UNKNOWN; + } + } catch (NullPointerException e) { + Log.e(TAG, "Event: something went wrong, exception caught, e = " + e); + mVendorSensorState = VENDOR_SENSOR_UNKNOWN; + } finally { + if (isDeviceInPocket != isDeviceInPocket()) { + dispatchCallbacks(); + } + } + } + + private void handleLightSensorEvent(SensorEvent sensorEvent) { + final boolean isDeviceInPocket = isDeviceInPocket(); + + mLastLightState = mLightState; + + if (DEBUG) { + final String sensorEventToString = sensorEvent != null ? sensorEvent.toString() : "NULL"; + Log.d(TAG, "LIGHT_SENSOR: onSensorChanged(), sensorEvent =" + sensorEventToString); + } + + try { + if (sensorEvent == null) { + if (DEBUG) Log.d(TAG, "Event is null!"); + mLightState = LIGHT_UNKNOWN; + } else if (sensorEvent.values == null || sensorEvent.values.length == 0) { + if (DEBUG) Log.d(TAG, "Event has no values! event.values null ? " + (sensorEvent.values == null)); + mLightState = LIGHT_UNKNOWN; + } else { + final float value = sensorEvent.values[0]; + final boolean isPoor = value >= 0 + && value <= POCKET_LIGHT_MAX_THRESHOLD; + if (DEBUG) { + final long time = SystemClock.uptimeMillis(); + Log.d(TAG, "Event: time= " + time + ", value=" + value + + ", maxRange=" + mLightMaxRange + ", isPoor=" + isPoor); + } + mLightState = isPoor ? LIGHT_POCKET : LIGHT_AMBIENT; + } + } catch (NullPointerException e) { + Log.e(TAG, "Event: something went wrong, exception caught, e = " + e); + mLightState = LIGHT_UNKNOWN; + } finally { + if (isDeviceInPocket != isDeviceInPocket()) { + dispatchCallbacks(); + } + } + } + + private void handleProximitySensorEvent(SensorEvent sensorEvent) { + final boolean isDeviceInPocket = isDeviceInPocket(); + + mLastProximityState = mProximityState; + + if (DEBUG) { + final String sensorEventToString = sensorEvent != null ? sensorEvent.toString() : "NULL"; + Log.d(TAG, "PROXIMITY_SENSOR: onSensorChanged(), sensorEvent =" + sensorEventToString); + } + + try { + if (sensorEvent == null) { + if (DEBUG) Log.d(TAG, "Event is null!"); + mProximityState = PROXIMITY_UNKNOWN; + } else if (sensorEvent.values == null || sensorEvent.values.length == 0) { + if (DEBUG) Log.d(TAG, "Event has no values! event.values null ? " + (sensorEvent.values == null)); + mProximityState = PROXIMITY_UNKNOWN; + } else { + final float value = sensorEvent.values[0]; + final boolean isPositive = sensorEvent.values[0] < mProximityMaxRange; + if (DEBUG) { + final long time = SystemClock.uptimeMillis(); + Log.d(TAG, "Event: time=" + time + ", value=" + value + + ", maxRange=" + mProximityMaxRange + ", isPositive=" + isPositive); + } + mProximityState = isPositive ? PROXIMITY_POSITIVE : PROXIMITY_NEGATIVE; + } + } catch (NullPointerException e) { + Log.e(TAG, "Event: something went wrong, exception caught, e = " + e); + mProximityState = PROXIMITY_UNKNOWN; + } finally { + if (isDeviceInPocket != isDeviceInPocket()) { + dispatchCallbacks(); + } + } + } + + private void handleUnregisterTimeout() { + mHandler.removeCallbacksAndMessages(null); + unregisterSensorListeners(); + } + + private static Sensor getSensor(SensorManager sm, String type) { + for (Sensor sensor : sm.getSensorList(Sensor.TYPE_ALL)) { + if (type.equals(sensor.getStringType()) && sensor.isWakeUpSensor()) { + return sensor; + } + } + return null; + } + + private void dispatchCallbacks() { + final boolean isDeviceInPocket = isDeviceInPocket(); + if (mInteractive) { + if (!isDeviceInPocket) { + mHandler.sendEmptyMessageDelayed(PocketHandler.MSG_UNREGISTER_TIMEOUT, 5000 /* ms */); + } else { + mHandler.removeMessages(PocketHandler.MSG_UNREGISTER_TIMEOUT); + } + } + mHandler.removeMessages(PocketHandler.MSG_DISPATCH_CALLBACKS); + mHandler.sendEmptyMessage(PocketHandler.MSG_DISPATCH_CALLBACKS); + } + + private void dumpInternal(PrintWriter pw) { + JSONObject dump = new JSONObject(); + try { + dump.put("service", "POCKET"); + dump.put("enabled", mEnabled); + dump.put("isDeviceInPocket", isDeviceInPocket()); + dump.put("interactive", mInteractive); + dump.put("proximityState", mProximityState); + dump.put("lastProximityState", mLastProximityState); + dump.put("proximityRegistered", mProximityRegistered); + dump.put("proximityMaxRange", mProximityMaxRange); + dump.put("lightState", mLightState); + dump.put("lastLightState", mLastLightState); + dump.put("lightRegistered", mLightRegistered); + dump.put("lightMaxRange", mLightMaxRange); + dump.put("VendorSensorState", mVendorSensorState); + dump.put("lastVendorSensorState", mLastVendorSensorState); + dump.put("VendorSensorRegistered", mVendorSensorRegistered); + } catch (JSONException e) { + Slog.e(TAG, "dump formatting failure", e); + } finally { + pw.println(dump); + } + } +} diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 1226032424f2..77b11f089e6a 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -167,6 +167,8 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.DeviceConfig; +import android.pocket.IPocketCallback; +import android.pocket.PocketManager; import android.provider.MediaStore; import android.provider.Settings; import android.provider.Settings.Secure; @@ -238,6 +240,7 @@ import com.android.server.policy.keyguard.KeyguardServiceDelegate; import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener; import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback; +import com.android.server.policy.pocket.PocketLock; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.vibrator.HapticFeedbackVibrationProvider; import com.android.server.vibrator.VibratorFrameworkStatsLogger; @@ -273,6 +276,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final String TAG = "WindowManager"; static final boolean localLOGV = false; + static final boolean DEBUG = false; static final boolean DEBUG_INPUT = false; static final boolean DEBUG_KEYGUARD = false; static final boolean DEBUG_WAKEUP = false; @@ -308,6 +312,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3; static final int LONG_PRESS_POWER_GO_TO_VOICE_ASSIST = 4; static final int LONG_PRESS_POWER_ASSISTANT = 5; // Settings.Secure.ASSISTANT + static final int LONG_PRESS_POWER_HIDE_POCKET_LOCK = 6; // must match: config_veryLongPresOnPowerBehavior in config.xml // The config value can be overridden using Settings.Global.POWER_BUTTON_VERY_LONG_PRESS @@ -725,6 +730,30 @@ public void onDrawn() { private final List mDeviceKeyHandlers = new ArrayList<>(); + private PocketManager mPocketManager; + private PocketLock mPocketLock; + private boolean mPocketLockShowing; + private boolean mIsDeviceInPocket; + private final IPocketCallback mPocketCallback = new IPocketCallback.Stub() { + + @Override + public void onStateChanged(boolean isDeviceInPocket, int reason) { + boolean wasDeviceInPocket = mIsDeviceInPocket; + if (reason == PocketManager.REASON_SENSOR) { + mIsDeviceInPocket = isDeviceInPocket; + } else { + mIsDeviceInPocket = false; + } + if (wasDeviceInPocket != mIsDeviceInPocket) { + handleDevicePocketStateChanged(); + //if (mKeyHandler != null) { + //mKeyHandler.setIsInPocket(mIsDeviceInPocket); + //} + } + } + + }; + private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3; private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4; private static final int MSG_KEYGUARD_DRAWN_COMPLETE = 5; @@ -1110,10 +1139,14 @@ private void interceptPowerKeyDown(KeyEvent event, boolean interactive) { // Inform the StatusBar; but do not allow it to consume the event. sendSystemKeyToStatusBarAsync(event); + // Abort possibly stuck animations. + mHandler.post(mWindowManagerFuncs::triggerAnimationFailsafe); + // If the power key has still not yet been handled, then detect short // press, long press, or multi press and decide what to do. mPowerKeyHandled = mPowerKeyHandled || hungUp || handledByPowerManager || mKeyCombinationManager.isPowerKeyIntercepted(); + if (!mPowerKeyHandled) { if (!interactive) { wakeUpFromWakeKey(event); @@ -1482,6 +1515,12 @@ private void powerLongPress(long eventTime) { launchAssistAction(null, powerKeyDeviceId, eventTime, AssistUtils.INVOCATION_TYPE_POWER_BUTTON_LONG_PRESS); break; + case LONG_PRESS_POWER_HIDE_POCKET_LOCK: + mPowerKeyHandled = true; + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, false, "Power - Long-Press - Hide Pocket Lock"); + hidePocketLock(true); + mPocketManager.setListeningExternal(false); + break; } } @@ -1545,6 +1584,10 @@ private int getResolvedLongPressOnPowerBehavior() { return LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM; } + if (mPocketLockShowing) { + return LONG_PRESS_POWER_HIDE_POCKET_LOCK; + } + // If the config indicates the assistant behavior but the device isn't yet provisioned, show // global actions instead. if (mLongPressOnPowerBehavior == LONG_PRESS_POWER_ASSISTANT && !isDeviceProvisioned()) { @@ -1823,7 +1866,9 @@ public void run() { }; private void handleScreenShot(@WindowManager.ScreenshotSource int source) { - mDefaultDisplayPolicy.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, source); + if (!mPocketLockShowing) { + mDefaultDisplayPolicy.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, source); + } } @Override @@ -4752,6 +4797,23 @@ && isWakeKeyWhenScreenOff(keyCode)) { + " policyFlags=" + Integer.toHexString(policyFlags)); } + // Pre-basic policy based on interactive and pocket lock state. + if (mIsDeviceInPocket && (!interactive || mPocketLockShowing)) { + if (keyCode != KeyEvent.KEYCODE_POWER && + keyCode != KeyEvent.KEYCODE_VOLUME_UP && + keyCode != KeyEvent.KEYCODE_VOLUME_DOWN && + keyCode != KeyEvent.KEYCODE_MEDIA_PLAY && + keyCode != KeyEvent.KEYCODE_MEDIA_PAUSE && + keyCode != KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE && + keyCode != KeyEvent.KEYCODE_HEADSETHOOK && + keyCode != KeyEvent.KEYCODE_MEDIA_STOP && + keyCode != KeyEvent.KEYCODE_MEDIA_NEXT && + keyCode != KeyEvent.KEYCODE_MEDIA_PREVIOUS && + keyCode != KeyEvent.KEYCODE_VOLUME_MUTE) { + return 0; + } + } + // Basic policy based on interactive state. int result; if (interactive || (isInjected && !isWakeKey)) { @@ -5605,6 +5667,9 @@ public void startedGoingToSleep(int displayGroupId, if (mKeyguardDelegate != null) { mKeyguardDelegate.onStartedGoingToSleep(pmSleepReason); } + if (mPocketManager != null) { + mPocketManager.onInteractiveChanged(false); + } } // Called on the PowerManager's Notifier thread. @@ -5678,6 +5743,10 @@ public void startedWakingUp(int displayGroupId, @WakeReason int pmWakeReason) { } mCameraGestureTriggered = false; + + if (mPocketManager != null) { + mPocketManager.onInteractiveChanged(true); + } } // Called on the PowerManager's Notifier thread. @@ -5951,6 +6020,72 @@ private void enableScreen(ScreenOnListener listener, boolean report) { } } + /** + * Perform operations if needed on pocket mode state changed. + * @see com.android.server.pocket.PocketService + * @see PocketLock + * @see this.mPocketCallback; + * @author Carlo Savignano + */ + private void handleDevicePocketStateChanged() { + final boolean interactive = mPowerManager.isInteractive(); + if (mIsDeviceInPocket) { + showPocketLock(interactive); + } else { + hidePocketLock(interactive); + } + } + + /** + * Check if we can show pocket lock once requested. + * @see com.android.server.pocket.PocketService + * @see PocketLock + * @see this.mPocketCallback; + * @author Carlo Savignano + */ + private void showPocketLock(boolean animate) { + if (!mSystemReady || !mSystemBooted || !mKeyguardDrawnOnce + || mPocketLock == null || mPocketLockShowing) { + return; + } + + if (mPowerManager.isInteractive() && !isKeyguardShowingAndNotOccluded()){ + return; + } + + if (DEBUG) { + Log.d(TAG, "showPocketLock, animate=" + animate); + } + + mPocketLock.show(animate); + mPocketLockShowing = true; + + mPocketManager.setPocketLockVisible(true); + } + + /** + * Check if we can hide pocket lock once requested. + * @see com.android.server.pocket.PocketService + * @see PocketLock + * @see this.mPocketCallback; + * @author Carlo Savignano + */ + private void hidePocketLock(boolean animate) { + if (!mSystemReady || !mSystemBooted || !mKeyguardDrawnOnce + || mPocketLock == null || !mPocketLockShowing) { + return; + } + + if (DEBUG) { + Log.d(TAG, "hidePocketLock, animate=" + animate); + } + + mPocketLock.hide(animate); + mPocketLockShowing = false; + + mPocketManager.setPocketLockVisible(false); + } + private void handleHideBootMessage() { synchronized (mLock) { if (!mKeyguardDrawnOnce) { @@ -6113,6 +6248,10 @@ public void systemReady() { // So it is better not to bind keyguard here. mKeyguardDelegate.onSystemReady(); + mPocketManager = (PocketManager) mContext.getSystemService(Context.POCKET_SERVICE); + mPocketManager.addCallback(mPocketCallback); + mPocketLock = new PocketLock(mContext); + mVrManagerInternal = LocalServices.getService(VrManagerInternal.class); if (mVrManagerInternal != null) { mVrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener); diff --git a/services/core/java/com/android/server/policy/pocket/PocketLock.java b/services/core/java/com/android/server/policy/pocket/PocketLock.java new file mode 100644 index 000000000000..f30d7722bbca --- /dev/null +++ b/services/core/java/com/android/server/policy/pocket/PocketLock.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2016 The ParanoidAndroid 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.server.policy.pocket; + +import android.animation.Animator; +import android.content.Context; +import android.graphics.PixelFormat; +import android.os.Handler; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; + +/** + * This class provides a fullscreen overlays view, displaying itself + * even on top of lock screen. While this view is displaying touch + * inputs are not passed to the the views below. + * @see android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; + * @author Carlo Savignano + */ +public class PocketLock { + + private final Context mContext; + private WindowManager mWindowManager; + private WindowManager.LayoutParams mLayoutParams; + private Handler mHandler; + private View mView; + private View mHintContainer; + + private boolean mAttached; + private boolean mAnimating; + + /** + * Creates pocket lock objects, inflate view and set layout parameters. + * @param context + */ + public PocketLock(Context context) { + mContext = context; + mHandler = new Handler(); + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mLayoutParams = getLayoutParams(); + mView = LayoutInflater.from(mContext).inflate( + com.android.internal.R.layout.pocket_lock_view, null); + } + + public void show(final boolean animate) { + final Runnable r = new Runnable() { + @Override + public void run() { + if (mAttached) { + return; + } + + if (mAnimating) { + mView.animate().cancel(); + } + + if (animate) { + mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mView.animate().alpha(1.0f).setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + mAnimating = true; + } + + @Override + public void onAnimationEnd(Animator animator) { + mView.setLayerType(View.LAYER_TYPE_NONE, null); + mAnimating = false; + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }).withStartAction(new Runnable() { + @Override + public void run() { + mView.setAlpha(0.0f); + mView.setVisibility(View.VISIBLE); + addView(); + } + }).start(); + } else { + mView.setVisibility(View.VISIBLE); + mView.setAlpha(1.0f); + addView(); + } + } + }; + + mHandler.post(r); + } + + public void hide(final boolean animate) { + final Runnable r = new Runnable() { + @Override + public void run() { + if (!mAttached) { + return; + } + + if (mAnimating) { + mView.animate().cancel(); + } + + if (animate) { + mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mView.animate().alpha(0.0f).setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + mAnimating = true; + } + + @Override + public void onAnimationEnd(Animator animator) { + mView.setVisibility(View.GONE); + mView.setLayerType(View.LAYER_TYPE_NONE, null); + mAnimating = false; + removeView(); + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }).start(); + } else { + mView.setVisibility(View.GONE); + mView.setAlpha(0.0f); + removeView(); + } + } + }; + + mHandler.post(r); + } + + private void addView() { + if (mWindowManager != null && !mAttached) { + mWindowManager.addView(mView, mLayoutParams); + mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + mAttached = true; + } + } + + private void removeView() { + if (mWindowManager != null && mAttached) { + mView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + mWindowManager.removeView(mView); + mAnimating = false; + mAttached = false; + } + } + + private WindowManager.LayoutParams getLayoutParams() { + mLayoutParams = new WindowManager.LayoutParams(); + mLayoutParams.format = PixelFormat.TRANSLUCENT; + mLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; + mLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; + mLayoutParams.gravity = Gravity.CENTER; + mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; + mLayoutParams.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + mLayoutParams.flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_FULLSCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; + return mLayoutParams; + } + +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index a43814cf1aee..1952c06b9606 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -217,6 +217,8 @@ import com.android.server.pm.permission.PermissionMigrationHelper; import com.android.server.pm.permission.PermissionMigrationHelperImpl; import com.android.server.pm.verify.domain.DomainVerificationService; +import com.android.server.pocket.PocketService; +import com.android.server.pocket.PocketBridgeService; import com.android.server.policy.AppOpsPolicy; import com.android.server.policy.PermissionPolicyService; import com.android.server.policy.PhoneWindowManager; @@ -2692,10 +2694,21 @@ private void startOtherServices(@NonNull TimingsTraceAndSlog t) { t.traceBegin("StartBackgroundInstallControlService"); mSystemServiceManager.startService(BackgroundInstallControlService.class); t.traceEnd(); - + t.traceBegin("StartHealthService"); mSystemServiceManager.startService(HealthInterfaceService.class); t.traceEnd(); + + t.traceBegin("StartPocketService"); + mSystemServiceManager.startService(PocketService.class); + t.traceEnd(); + + if (!context.getResources().getString( + com.android.internal.R.string.config_pocketBridgeSysfsInpocket).isEmpty()) { + t.traceBegin("StartPocketBridgeService"); + mSystemServiceManager.startService(PocketBridgeService.class); + t.traceEnd(); + } } t.traceBegin("StartMediaProjectionManager");