diff options
50 files changed, 1593 insertions, 309 deletions
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl index 84b6869bf620..90392e65794a 100644 --- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl +++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl @@ -38,4 +38,5 @@ oneway interface IWallpaperEngine { @UnsupportedAppUsage void destroy(); void setZoomOut(float scale); + void scalePreview(in Rect positionInWindow); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index f944dd78dc3d..1fd192c656b9 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -16,6 +16,11 @@ package android.service.wallpaper; +import static android.graphics.Matrix.MSCALE_X; +import static android.graphics.Matrix.MSCALE_Y; +import static android.graphics.Matrix.MSKEW_X; +import static android.graphics.Matrix.MSKEW_Y; + import android.annotation.FloatRange; import android.annotation.Nullable; import android.annotation.SdkConstant; @@ -31,6 +36,7 @@ import android.content.Intent; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; @@ -123,7 +129,8 @@ public abstract class WallpaperService extends Service { private static final int MSG_WINDOW_MOVED = 10035; private static final int MSG_TOUCH_EVENT = 10040; private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050; - private static final int MSG_SCALE = 10100; + private static final int MSG_ZOOM = 10100; + private static final int MSG_SCALE_PREVIEW = 10110; private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000; @@ -178,6 +185,7 @@ public abstract class WallpaperService extends Service { WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS; int mCurWindowFlags = mWindowFlags; int mCurWindowPrivateFlags = mWindowPrivateFlags; + Rect mPreviewSurfacePosition; final Rect mVisibleInsets = new Rect(); final Rect mWinFrame = new Rect(); final Rect mContentInsets = new Rect(); @@ -194,6 +202,8 @@ public abstract class WallpaperService extends Service { final InsetsSourceControl[] mTempControls = new InsetsSourceControl[0]; final MergedConfiguration mMergedConfiguration = new MergedConfiguration(); private final Point mSurfaceSize = new Point(); + private final Matrix mTmpMatrix = new Matrix(); + private final float[] mTmpValues = new float[9]; final WindowManager.LayoutParams mLayout = new WindowManager.LayoutParams(); @@ -366,7 +376,7 @@ public abstract class WallpaperService extends Service { Message msg = mCaller.obtainMessage(MSG_WALLPAPER_OFFSETS); mCaller.sendMessage(msg); } - Message msg = mCaller.obtainMessageI(MSG_SCALE, Float.floatToIntBits(zoom)); + Message msg = mCaller.obtainMessageI(MSG_ZOOM, Float.floatToIntBits(zoom)); mCaller.sendMessage(msg); } } @@ -747,6 +757,8 @@ public abstract class WallpaperService extends Service { out.println(mMergedConfiguration.getMergedConfiguration()); out.print(prefix); out.print("mLayout="); out.println(mLayout); out.print(prefix); out.print("mZoom="); out.println(mZoom); + out.print(prefix); out.print("mPreviewSurfacePosition="); + out.println(mPreviewSurfacePosition); synchronized (mLock) { out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset); out.print(" mPendingXOffset="); out.println(mPendingXOffset); @@ -908,7 +920,6 @@ public abstract class WallpaperService extends Service { mInsetsState, mTempControls, mSurfaceSize, mTmpSurfaceControl); if (mSurfaceControl.isValid()) { mSurfaceHolder.mSurface.copyFrom(mSurfaceControl); - mSurfaceControl.release(); } if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface @@ -1063,6 +1074,7 @@ public abstract class WallpaperService extends Service { if (redrawNeeded) { mSession.finishDrawing(mWindow, null /* postDrawTransaction */); } + reposition(); mIWallpaperEngine.reportShown(); } } catch (RemoteException ex) { @@ -1073,6 +1085,39 @@ public abstract class WallpaperService extends Service { } } + private void scalePreview(Rect position) { + if (isPreview() && mPreviewSurfacePosition == null && position != null + || mPreviewSurfacePosition != null + && !mPreviewSurfacePosition.equals(position)) { + mPreviewSurfacePosition = position; + if (mSurfaceControl.isValid()) { + reposition(); + } else { + updateSurface(false, false, false); + } + } + } + + private void reposition() { + if (mPreviewSurfacePosition == null) { + return; + } + if (DEBUG) { + Log.i(TAG, "reposition: rect: " + mPreviewSurfacePosition); + } + + mTmpMatrix.setTranslate(mPreviewSurfacePosition.left, mPreviewSurfacePosition.top); + mTmpMatrix.postScale(((float) mPreviewSurfacePosition.width()) / mCurWidth, + ((float) mPreviewSurfacePosition.height()) / mCurHeight); + mTmpMatrix.getValues(mTmpValues); + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.setPosition(mSurfaceControl, mPreviewSurfacePosition.left, + mPreviewSurfacePosition.top); + t.setMatrix(mSurfaceControl, mTmpValues[MSCALE_X], mTmpValues[MSKEW_Y], + mTmpValues[MSKEW_X], mTmpValues[MSCALE_Y]); + t.apply(); + } + void attach(IWallpaperEngineWrapper wrapper) { if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper); if (mDestroyed) { @@ -1414,7 +1459,7 @@ public abstract class WallpaperService extends Service { } public void setZoomOut(float scale) { - Message msg = mCaller.obtainMessageI(MSG_SCALE, Float.floatToIntBits(scale)); + Message msg = mCaller.obtainMessageI(MSG_ZOOM, Float.floatToIntBits(scale)); mCaller.sendMessage(msg); } @@ -1444,6 +1489,11 @@ public abstract class WallpaperService extends Service { mDetached.set(true); } + public void scalePreview(Rect position) { + Message msg = mCaller.obtainMessageO(MSG_SCALE_PREVIEW, position); + mCaller.sendMessage(msg); + } + private void doDetachEngine() { mActiveEngines.remove(mEngine); mEngine.detach(); @@ -1490,9 +1540,12 @@ public abstract class WallpaperService extends Service { case MSG_UPDATE_SURFACE: mEngine.updateSurface(true, false, false); break; - case MSG_SCALE: + case MSG_ZOOM: mEngine.setZoom(Float.intBitsToFloat(message.arg1)); break; + case MSG_SCALE_PREVIEW: + mEngine.scalePreview((Rect) message.obj); + break; case MSG_VISIBILITY_CHANGED: if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine + ": " + message.arg1); diff --git a/data/sounds/effects/ChargingStarted.ogg b/data/sounds/effects/ChargingStarted.ogg Binary files differindex f09e273ea5ac..9526b08d9b3c 100644 --- a/data/sounds/effects/ChargingStarted.ogg +++ b/data/sounds/effects/ChargingStarted.ogg diff --git a/data/sounds/effects/ogg/ChargingStarted.ogg b/data/sounds/effects/ogg/ChargingStarted.ogg Binary files differindex f09e273ea5ac..9526b08d9b3c 100644 --- a/data/sounds/effects/ogg/ChargingStarted.ogg +++ b/data/sounds/effects/ogg/ChargingStarted.ogg diff --git a/data/sounds/effects/ogg/ChargingStarted_48k.ogg b/data/sounds/effects/ogg/ChargingStarted_48k.ogg Binary files differindex f09e273ea5ac..9526b08d9b3c 100644 --- a/data/sounds/effects/ogg/ChargingStarted_48k.ogg +++ b/data/sounds/effects/ogg/ChargingStarted_48k.ogg diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java index f066bf589b64..948091c12721 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java @@ -113,12 +113,11 @@ public abstract class CarSystemUIModule { @Binds @Singleton - public abstract BatteryController provideBatteryController( - BatteryControllerImpl controllerImpl); + public abstract BatteryController bindBatteryController(BatteryControllerImpl controllerImpl); @Binds @Singleton - public abstract QSFactory provideQSFactory(QSFactoryImpl qsFactoryImpl); + public abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl); @Binds abstract DockManager bindDockManager(DockManagerImpl dockManager); diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 62335abd4329..3395c71bd5a3 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -205,6 +205,14 @@ far break points. A sensor value less than this is considered "near". --> <item name="proximity_sensor_threshold" translatable="false" format="float" type="dimen"></item> + <!-- Override value to use for proximity sensor as confirmation for proximity_sensor_type. --> + <string name="proximity_sensor_secondary_type" translatable="false"></string> + + <!-- If using proximity_sensor_confirmation_type, specifies a threshold value to distinguish + near and far break points. A sensor value less than this is considered "near". --> + <item name="proximity_sensor_secondary_threshold" translatable="false" format="float" + type="dimen"></item> + <!-- Doze: pulse parameter - how long does it take to fade in? --> <integer name="doze_pulse_duration_in">130</integer> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java new file mode 100644 index 000000000000..8a652cb22bb9 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperEngineCompat.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shared.system; + +import android.graphics.Rect; +import android.os.RemoteException; +import android.service.wallpaper.IWallpaperEngine; +import android.util.Log; + +/** + * @see IWallpaperEngine + */ +public class WallpaperEngineCompat { + + private static final String TAG = "WallpaperEngineCompat"; + + private final IWallpaperEngine mWrappedEngine; + + public WallpaperEngineCompat(IWallpaperEngine wrappedEngine) { + mWrappedEngine = wrappedEngine; + } + + /** + * @see IWallpaperEngine#scalePreview(Rect) + */ + public void scalePreview(Rect scaleToRect) { + try { + mWrappedEngine.scalePreview(scaleToRect); + } catch (RemoteException e) { + Log.i(TAG, "Couldn't call scalePreview method on WallpaperEngine", e); + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperManagerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperManagerCompat.java index 7570c2cbfe98..af804837228e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperManagerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WallpaperManagerCompat.java @@ -36,4 +36,12 @@ public class WallpaperManagerCompat { public void setWallpaperZoomOut(IBinder windowToken, float zoom) { mWallpaperManager.setWallpaperZoomOut(windowToken, zoom); } + + /** + * @return the max scale for the wallpaper when it's fully zoomed out + */ + public static float getWallpaperZoomOutMaxScale(Context context) { + return context.getResources() + .getFloat(com.android.internal.R.dimen.config_wallpaperMaxScale); + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 87990cd3bffa..ced77beff642 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -74,6 +74,7 @@ public final class Prefs { Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, Key.HAS_SEEN_BUBBLES_EDUCATION, Key.HAS_SEEN_BUBBLES_MANAGE_EDUCATION, + Key.HAS_SEEN_REVERSE_BOTTOM_SHEET, Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT }) public @interface Key { @@ -122,6 +123,7 @@ public final class Prefs { String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip"; String HAS_SEEN_BUBBLES_EDUCATION = "HasSeenBubblesOnboarding"; String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding"; + String HAS_SEEN_REVERSE_BOTTOM_SHEET = "HasSeenReverseBottomSheet"; String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount"; /** Tracks whether the user has seen the onboarding screen for priority conversations */ String HAS_SEEN_PRIORITY_ONBOARDING = "HasSeenPriorityOnboarding"; diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java index 79b691bb3e37..cab805848e7b 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerProxy.java @@ -87,7 +87,7 @@ public class FalsingManagerProxy implements FalsingManager, Dumpable { mUiBgExecutor = uiBgExecutor; mStatusBarStateController = statusBarStateController; mProximitySensor.setTag(PROXIMITY_SENSOR_TAG); - mProximitySensor.setSensorDelay(SensorManager.SENSOR_DELAY_GAME); + mProximitySensor.setDelay(SensorManager.SENSOR_DELAY_GAME); mDeviceConfig = deviceConfig; mDeviceConfigListener = properties -> onDeviceConfigPropertiesChanged(context, properties.getNamespace()); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java index caab18712b0b..fad6aaf06ab3 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/BrightLineFalsingManager.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.sensors.ThresholdSensor; import java.io.PrintWriter; import java.util.ArrayDeque; @@ -76,7 +77,7 @@ public class BrightLineFalsingManager implements FalsingManager { private final List<FalsingClassifier> mClassifiers; - private ProximitySensor.ProximitySensorListener mSensorEventListener = this::onProximityEvent; + private ThresholdSensor.Listener mSensorEventListener = this::onProximityEvent; private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback = new KeyguardUpdateMonitorCallback() { @@ -240,7 +241,7 @@ public class BrightLineFalsingManager implements FalsingManager { mClassifiers.forEach((classifier) -> classifier.onTouchEvent(motionEvent)); } - private void onProximityEvent(ProximitySensor.ProximityEvent proximityEvent) { + private void onProximityEvent(ThresholdSensor.ThresholdSensorEvent proximityEvent) { // TODO: some of these classifiers might allow us to abort early, meaning we don't have to // make these calls. mClassifiers.forEach((classifier) -> classifier.onProximityEvent(proximityEvent)); diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java index cf088213644e..85e95a66bfe3 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/FalsingClassifier.java @@ -96,7 +96,7 @@ abstract class FalsingClassifier { /** * Called when a ProximityEvent occurs (change in near/far). */ - void onProximityEvent(ProximitySensor.ProximityEvent proximityEvent) {}; + void onProximityEvent(ProximitySensor.ThresholdSensorEvent proximityEvent) {}; /** * The phone screen has turned on and we need to begin falsing detection. diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java index 749914e1b625..b128678af5db 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/ProximityClassifier.java @@ -101,8 +101,8 @@ class ProximityClassifier extends FalsingClassifier { @Override public void onProximityEvent( - ProximitySensor.ProximityEvent proximityEvent) { - boolean near = proximityEvent.getNear(); + ProximitySensor.ThresholdSensorEvent proximityEvent) { + boolean near = proximityEvent.getBelow(); long timestampNs = proximityEvent.getTimestampNs(); logDebug("Sensor is: " + near + " at time " + timestampNs); update(near, timestampNs); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java index 2b27436c85dd..20b343fed2eb 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java @@ -36,6 +36,7 @@ import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutManager; import android.content.res.Resources; +import android.hardware.SensorManager; import android.hardware.SensorPrivacyManager; import android.hardware.display.DisplayManager; import android.media.AudioManager; @@ -247,6 +248,12 @@ public class SystemServicesModule { return context.getResources(); } + @Provides + @Singleton + static SensorManager providesSensorManager(Context context) { + return context.getSystemService(SensorManager.class); + } + @Singleton @Provides static SensorPrivacyManager provideSensorPrivacyManager(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java index 8c572fe8f842..7e5a3ae83928 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java @@ -83,12 +83,11 @@ public abstract class SystemUIDefaultModule { @Binds @Singleton - public abstract BatteryController provideBatteryController( - BatteryControllerImpl controllerImpl); + public abstract BatteryController bindBatteryController(BatteryControllerImpl controllerImpl); @Binds @Singleton - public abstract QSFactory provideQSFactory(QSFactoryImpl qsFactoryImpl); + public abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl); @Binds abstract DockManager bindDockManager(DockManagerImpl dockManager); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 90cd13fd1330..cb45926d3f24 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -43,6 +43,7 @@ import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.concurrency.ConcurrencyModule; import com.android.systemui.util.sensors.AsyncSensorManager; +import com.android.systemui.util.sensors.SensorModule; import com.android.systemui.util.time.SystemClock; import com.android.systemui.util.time.SystemClockImpl; @@ -62,7 +63,8 @@ import dagger.Provides; ConcurrencyModule.class, LogModule.class, PeopleHubModule.class, - SettingsModule.class + SensorModule.class, + SettingsModule.class, }, subcomponents = {StatusBarComponent.class, NotificationRowComponent.class, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 10776c91df84..e4da345af465 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -81,7 +81,8 @@ public class DozeSensors { public DozeSensors(Context context, AlarmManager alarmManager, AsyncSensorManager sensorManager, DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, - Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog) { + Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog, + ProximitySensor proximitySensor) { mContext = context; mAlarmManager = alarmManager; mSensorManager = sensorManager; @@ -153,12 +154,12 @@ public class DozeSensors { dozeLog), }; - mProximitySensor = new ProximitySensor(context.getResources(), sensorManager); + mProximitySensor = proximitySensor; setProxListening(false); // Don't immediately start listening when we register. mProximitySensor.register( proximityEvent -> { if (proximityEvent != null) { - mProxCallback.accept(!proximityEvent.getNear()); + mProxCallback.accept(!proximityEvent.getBelow()); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 1be4d438683a..01478821a348 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -92,8 +92,8 @@ public class DozeTriggers implements DozeMachine.Part { AlarmManager alarmManager, AmbientDisplayConfiguration config, DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler, WakeLock wakeLock, boolean allowPulseTriggers, DockManager dockManager, - ProximitySensor proximitySensor, - DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher) { + ProximitySensor proximitySensor, DozeLog dozeLog, + BroadcastDispatcher broadcastDispatcher) { mContext = context; mMachine = machine; mDozeHost = dozeHost; @@ -103,7 +103,8 @@ public class DozeTriggers implements DozeMachine.Part { mWakeLock = wakeLock; mAllowPulseTriggers = allowPulseTriggers; mDozeSensors = new DozeSensors(context, alarmManager, mSensorManager, dozeParameters, - config, wakeLock, this::onSensor, this::onProximityFar, dozeLog); + config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, + proximitySensor); mUiModeManager = mContext.getSystemService(UiModeManager.class); mDockManager = dockManager; mProxCheck = new ProximitySensor.ProximityCheck(proximitySensor, handler); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index 0680c7f70b97..12ea1004038e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -45,10 +45,10 @@ public class AutoTileManager { public static final String NIGHT = "night"; public static final String CAST = "cast"; - private final Context mContext; - private final QSTileHost mHost; - private final Handler mHandler; - private final AutoAddTracker mAutoTracker; + protected final Context mContext; + protected final QSTileHost mHost; + protected final Handler mHandler; + protected final AutoAddTracker mAutoTracker; private final HotspotController mHotspotController; private final DataSaverController mDataSaverController; private final ManagedProfileController mManagedProfileController; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java index a81189eaeaf8..21e2e580df4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java @@ -53,6 +53,11 @@ public interface BatteryController extends DemoMode, Dumpable, boolean isAodPowerSave(); /** + * Returns {@code true} if the device is currently in wireless charging mode. + */ + default boolean isWirelessCharging() { return false; } + + /** * Returns {@code true} if reverse is supported. */ default boolean isReverseSupported() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 95b41a141244..98d01464debd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -58,7 +58,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final EnhancedEstimates mEstimates; - private final BroadcastDispatcher mBroadcastDispatcher; + protected final BroadcastDispatcher mBroadcastDispatcher; protected final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>(); private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>(); @@ -73,6 +73,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private boolean mCharged; protected boolean mPowerSave; private boolean mAodPowerSave; + protected boolean mWirelessCharging; private boolean mTestmode = false; private boolean mHasReceivedBattery = false; private Estimate mEstimate; @@ -150,6 +151,8 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC BatteryManager.BATTERY_STATUS_UNKNOWN); mCharged = status == BatteryManager.BATTERY_STATUS_FULL; mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING; + mWirelessCharging = mCharging && intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) + == BatteryManager.BATTERY_PLUGGED_WIRELESS; fireBatteryLevelChanged(); } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) { @@ -205,6 +208,11 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } @Override + public boolean isWirelessCharging() { + return mWirelessCharging; + } + + @Override public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) { // Need to fetch or refresh the estimate, but it may involve binder calls so offload the // work diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java index 2224c9cce40a..450336a6b73b 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java @@ -60,8 +60,8 @@ public class AsyncSensorManager extends SensorManager private final List<SensorManagerPlugin> mPlugins; @Inject - public AsyncSensorManager(Context context, PluginManager pluginManager) { - this(context.getSystemService(SensorManager.class), pluginManager, null); + public AsyncSensorManager(SensorManager sensorManager, PluginManager pluginManager) { + this(sensorManager, pluginManager, null); } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/PrimaryProxSensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/PrimaryProxSensor.java new file mode 100644 index 000000000000..96c76c1a15d0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/PrimaryProxSensor.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.sensors; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +@interface PrimaryProxSensor { +} diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java index 378dde284747..9be42f3fc5f4 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java @@ -16,92 +16,119 @@ package com.android.systemui.util.sensors; -import android.content.res.Resources; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Handler; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.Assert; +import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.function.Consumer; import javax.inject.Inject; /** - * Simple wrapper around SensorManager customized for the Proximity sensor. + * Wrapper around SensorManager customized for the Proximity sensor. + * + * The ProximitySensor supports the concept of a primary and a + * secondary hardware sensor. The primary sensor is used for a first + * pass check if the phone covered. When triggered, it then checks + * the secondary sensor for confirmation (if there is one). It does + * not send a proximity event until the secondary sensor confirms (or + * rejects) the reading. The secondary sensor is, in fact, the source + * of truth. + * + * This is necessary as sometimes keeping the secondary sensor on for + * extends periods is undesirable. It may, however, result in increased + * latency for proximity readings. + * + * Phones should configure this via a config.xml overlay. If no + * proximity sensor is set (primary or secondary) we fall back to the + * default Sensor.TYPE_PROXIMITY. If proximity_sensor_type is set in + * config.xml, that will be used as the primary sensor. If + * proximity_sensor_secondary_type is set, that will function as the + * secondary sensor. If no secondary is set, only the primary will be + * used. */ -public class ProximitySensor { +public class ProximitySensor implements ThresholdSensor { private static final String TAG = "ProxSensor"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private static final long SECONDARY_PING_INTERVAL_MS = 5000; - private final Sensor mSensor; - private final AsyncSensorManager mSensorManager; - private final float mThreshold; - private List<ProximitySensorListener> mListeners = new ArrayList<>(); + private final ThresholdSensor mPrimaryThresholdSensor; + private final ThresholdSensor mSecondaryThresholdSensor; + private final DelayableExecutor mDelayableExecutor; + private final List<ThresholdSensor.Listener> mListeners = new ArrayList<>(); private String mTag = null; - @VisibleForTesting ProximityEvent mLastEvent; - private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL; + private ThresholdSensorEvent mLastPrimaryEvent; + @VisibleForTesting + ThresholdSensorEvent mLastEvent; private boolean mPaused; private boolean mRegistered; + private Runnable mCancelSecondaryRunnable; + private boolean mInitializedListeners = false; - private SensorEventListener mSensorEventListener = new SensorEventListener() { + private ThresholdSensor.Listener mPrimaryEventListener = new ThresholdSensor.Listener() { @Override - public synchronized void onSensorChanged(SensorEvent event) { - onSensorEvent(event); + public void onThresholdCrossed(ThresholdSensorEvent event) { + onPrimarySensorEvent(event); } + }; + private ThresholdSensor.Listener mSecondaryEventListener = new ThresholdSensor.Listener() { @Override - public void onAccuracyChanged(Sensor sensor, int accuracy) { + public void onThresholdCrossed(ThresholdSensorEvent event) { + // This sensor should only be used briefly. Turn it off as soon as we get a reading. + mSecondaryThresholdSensor.pause(); + + // Only check the secondary as long as the primary thinks we're near. + if (!mLastPrimaryEvent.getBelow()) { + mCancelSecondaryRunnable = null; + return; + } + logDebug("Secondary sensor event: " + event.getBelow() + "."); + + onSensorEvent(event); + + // Check this sensor again in a moment. + mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed( + mSecondaryThresholdSensor::resume, SECONDARY_PING_INTERVAL_MS); } }; @Inject - public ProximitySensor(@Main Resources resources, - AsyncSensorManager sensorManager) { - mSensorManager = sensorManager; - - Sensor sensor = findCustomProxSensor(resources); - float threshold = 0; - if (sensor != null) { - try { - threshold = getCustomProxThreshold(resources); - } catch (IllegalStateException e) { - Log.e(TAG, "Can not load custom proximity sensor.", e); - sensor = null; - } - } - if (sensor == null) { - sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); - if (sensor != null) { - threshold = sensor.getMaximumRange(); - } - } - - mThreshold = threshold; - - mSensor = sensor; + public ProximitySensor(@PrimaryProxSensor ThresholdSensor primary, + @SecondaryProxSensor ThresholdSensor secondary, + @Main DelayableExecutor delayableExecutor) { + mPrimaryThresholdSensor = primary; + mSecondaryThresholdSensor = secondary; + mDelayableExecutor = delayableExecutor; } + @Override public void setTag(String tag) { mTag = tag; + mPrimaryThresholdSensor.setTag(tag + ":primary"); + mSecondaryThresholdSensor.setTag(tag + ":secondary"); } - public void setSensorDelay(int sensorDelay) { - mSensorDelay = sensorDelay; + @Override + public void setDelay(int delay) { + Assert.isMainThread(); + mPrimaryThresholdSensor.setDelay(delay); + mSecondaryThresholdSensor.setDelay(delay); } /** * Unregister with the {@link SensorManager} without unsetting listeners on this object. */ + @Override public void pause() { + Assert.isMainThread(); mPaused = true; unregisterInternal(); } @@ -109,41 +136,13 @@ public class ProximitySensor { /** * Register with the {@link SensorManager}. No-op if no listeners are registered on this object. */ + @Override public void resume() { + Assert.isMainThread(); mPaused = false; registerInternal(); } - /** - * Returns a brightness sensor that can be used for proximity purposes. - */ - private Sensor findCustomProxSensor(Resources resources) { - String sensorType = resources.getString(R.string.proximity_sensor_type); - if (sensorType.isEmpty()) { - return null; - } - List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL); - Sensor sensor = null; - for (Sensor s : sensorList) { - if (sensorType.equals(s.getStringType())) { - sensor = s; - break; - } - } - - return sensor; - } - - /** - * Returns a threshold value that can be used along with {@link #findCustomProxSensor} - */ - private float getCustomProxThreshold(Resources resources) { - try { - return resources.getFloat(R.dimen.proximity_sensor_threshold); - } catch (Resources.NotFoundException e) { - throw new IllegalStateException("R.dimen.proximity_sensor_threshold must be set."); - } - } /** * Returns true if we are registered with the SensorManager. @@ -155,38 +154,46 @@ public class ProximitySensor { /** * Returns {@code false} if a Proximity sensor is not available. */ - public boolean getSensorAvailable() { - return mSensor != null; + @Override + public boolean isLoaded() { + return mPrimaryThresholdSensor.isLoaded(); } /** * Add a listener. * * Registers itself with the {@link SensorManager} if this is the first listener - * added. If a cool down is currently running, the sensor will be registered when it is over. + * added. If the ProximitySensor is paused, it will be registered when resumed. */ - public boolean register(ProximitySensorListener listener) { - if (!getSensorAvailable()) { - return false; + @Override + public void register(ThresholdSensor.Listener listener) { + Assert.isMainThread(); + if (!isLoaded()) { + return; } if (mListeners.contains(listener)) { - Log.d(TAG, "ProxListener registered multiple times: " + listener); + logDebug("ProxListener registered multiple times: " + listener); } else { mListeners.add(listener); } registerInternal(); - - return true; } protected void registerInternal() { + Assert.isMainThread(); if (mRegistered || mPaused || mListeners.isEmpty()) { return; } + if (!mInitializedListeners) { + mPrimaryThresholdSensor.register(mPrimaryEventListener); + mSecondaryThresholdSensor.pause(); + mSecondaryThresholdSensor.register(mSecondaryEventListener); + mInitializedListeners = true; + } logDebug("Registering sensor listener"); + mPrimaryThresholdSensor.resume(); mRegistered = true; - mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay); } /** @@ -195,7 +202,9 @@ public class ProximitySensor { * If all listeners are removed from an instance of this class, * it will unregister itself with the SensorManager. */ - public void unregister(ProximitySensorListener listener) { + @Override + public void unregister(ThresholdSensor.Listener listener) { + Assert.isMainThread(); mListeners.remove(listener); if (mListeners.size() == 0) { unregisterInternal(); @@ -203,34 +212,77 @@ public class ProximitySensor { } protected void unregisterInternal() { + Assert.isMainThread(); if (!mRegistered) { return; } logDebug("unregistering sensor listener"); - mSensorManager.unregisterListener(mSensorEventListener); + mPrimaryThresholdSensor.pause(); + mSecondaryThresholdSensor.pause(); + if (mCancelSecondaryRunnable != null) { + mCancelSecondaryRunnable.run(); + mCancelSecondaryRunnable = null; + } + mLastPrimaryEvent = null; // Forget what we know. + mLastEvent = null; mRegistered = false; } public Boolean isNear() { - return getSensorAvailable() && mLastEvent != null ? mLastEvent.getNear() : null; + return isLoaded() && mLastEvent != null ? mLastEvent.getBelow() : null; } /** Update all listeners with the last value this class received from the sensor. */ public void alertListeners() { - mListeners.forEach(proximitySensorListener -> - proximitySensorListener.onSensorEvent(mLastEvent)); + Assert.isMainThread(); + if (mLastEvent == null) { + return; + } + + List<ThresholdSensor.Listener> listeners = new ArrayList<>(mListeners); + listeners.forEach(proximitySensorListener -> + proximitySensorListener.onThresholdCrossed(mLastEvent)); + } + + private void onPrimarySensorEvent(ThresholdSensorEvent event) { + Assert.isMainThread(); + if (mLastPrimaryEvent != null && event.getBelow() == mLastPrimaryEvent.getBelow()) { + return; + } + + mLastPrimaryEvent = event; + + if (event.getBelow() && mSecondaryThresholdSensor.isLoaded()) { + logDebug("Primary sensor is near. Checking secondary."); + if (mCancelSecondaryRunnable == null) { + mSecondaryThresholdSensor.resume(); + } + } else { + if (!mSecondaryThresholdSensor.isLoaded()) { + logDebug("Primary sensor event: " + event.getBelow() + ". No secondary."); + } else { + logDebug("Primary sensor event: " + event.getBelow() + "."); + } + onSensorEvent(event); + } } - private void onSensorEvent(SensorEvent event) { - boolean near = event.values[0] < mThreshold; - mLastEvent = new ProximityEvent(near, event.timestamp); + private void onSensorEvent(ThresholdSensorEvent event) { + Assert.isMainThread(); + if (mLastEvent != null && event.getBelow() == mLastEvent.getBelow()) { + return; + } + + mLastEvent = event; alertListeners(); } @Override public String toString() { - return String.format("{registered=%s, paused=%s, near=%s, sensor=%s}", - isRegistered(), mPaused, isNear(), mSensor); + return String.format("{registered=%s, paused=%s, near=%s, primarySensor=%s, " + + "secondarySensor=%s}", + isRegistered(), mPaused, isNear(), mPrimaryThresholdSensor, + mSecondaryThresholdSensor); } /** @@ -248,11 +300,11 @@ public class ProximitySensor { mSensor.setTag("prox_check"); mHandler = handler; mSensor.pause(); - ProximitySensorListener listener = proximityEvent -> { + ThresholdSensor.Listener listener = proximityEvent -> { mCallbacks.forEach( booleanConsumer -> booleanConsumer.accept( - proximityEvent == null ? null : proximityEvent.getNear())); + proximityEvent == null ? null : proximityEvent.getBelow())); mCallbacks.clear(); mSensor.pause(); }; @@ -274,7 +326,7 @@ public class ProximitySensor { * Query the proximity sensor, timing out if no result. */ public void check(long timeoutMs, Consumer<Boolean> callback) { - if (!mSensor.getSensorAvailable()) { + if (!mSensor.isLoaded()) { callback.accept(null); } mCallbacks.add(callback); @@ -285,43 +337,6 @@ public class ProximitySensor { } } - /** Implement to be notified of ProximityEvents. */ - public interface ProximitySensorListener { - /** Called when the ProximitySensor changes. */ - void onSensorEvent(ProximityEvent proximityEvent); - } - - /** - * Returned when the near/far state of a {@link ProximitySensor} changes. - */ - public static class ProximityEvent { - private final boolean mNear; - private final long mTimestampNs; - - public ProximityEvent(boolean near, long timestampNs) { - mNear = near; - mTimestampNs = timestampNs; - } - - public boolean getNear() { - return mNear; - } - - public long getTimestampNs() { - return mTimestampNs; - } - - public long getTimestampMs() { - return mTimestampNs / 1000000; - } - - @Override - public String toString() { - return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mNear, mTimestampNs); - } - - } - private void logDebug(String msg) { if (DEBUG) { Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg); diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/SecondaryProxSensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/SecondaryProxSensor.java new file mode 100644 index 000000000000..89fc0eabf607 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/SecondaryProxSensor.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.sensors; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +@Qualifier +@Documented +@Retention(RUNTIME) +@interface SecondaryProxSensor { +} diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java new file mode 100644 index 000000000000..5a93e6c9f77a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/SensorModule.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.sensors; + +import android.hardware.Sensor; +import android.hardware.SensorManager; + +import com.android.systemui.R; + +import dagger.Module; +import dagger.Provides; + +/** + * Dagger module for Sensor related classes. + */ +@Module +public class SensorModule { + @Provides + @PrimaryProxSensor + static ThresholdSensor providePrimaryProxSensor(SensorManager sensorManager, + ThresholdSensorImpl.Builder thresholdSensorBuilder) { + try { + return thresholdSensorBuilder + .setSensorDelay(SensorManager.SENSOR_DELAY_NORMAL) + .setSensorResourceId(R.string.proximity_sensor_type) + .setThresholdResourceId(R.dimen.proximity_sensor_threshold) + .build(); + } catch (IllegalStateException e) { + Sensor defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY); + return thresholdSensorBuilder + .setSensor(defaultSensor) + .setThresholdValue(defaultSensor.getMaximumRange()) + .build(); + } + } + + @Provides + @SecondaryProxSensor + static ThresholdSensor provideSecondaryProxSensor( + ThresholdSensorImpl.Builder thresholdSensorBuilder) { + try { + return thresholdSensorBuilder + .setSensorResourceId(R.string.proximity_sensor_secondary_type) + .setThresholdResourceId(R.dimen.proximity_sensor_secondary_threshold) + .build(); + } catch (IllegalStateException e) { + return thresholdSensorBuilder.setSensor(null).setThresholdValue(0).build(); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java new file mode 100644 index 000000000000..363a734a6ae5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensor.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.sensors; + +import java.util.Locale; + +/** + * A wrapper class for sensors that have a boolean state - above/below. + */ +public interface ThresholdSensor { + /** + * Optional label to use for logging. + * + * This should be set to something meaningful by owner of the instance. + */ + void setTag(String tag); + + /** + * Change the delay used when registering the sensor. + * + * If the sensor is already registered, this should cause it to re-register with the new + * delay. + */ + void setDelay(int delay); + + /** + * True if this sensor successfully loads and can be listened to. + */ + boolean isLoaded(); + + /** + * Registers with the sensor and calls the supplied callback on value change. + * + * If this instance is paused, the listener will be recorded, but no registration with + * the underlying physical sensor will occur until {@link #resume()} is called. + * + * @see #unregister(Listener) + */ + void register(Listener listener); + + /** + * Unregisters from the physical sensor without removing any supplied listeners. + * + * No events will be sent to listeners as long as this sensor is paused. + * + * @see #resume() + * @see #unregister(Listener) + */ + void pause(); + + /** + * Resumes listening to the physical sensor after previously pausing. + * + * @see #pause() + */ + void resume(); + + /** + * Unregister a listener with the sensor. + * + * @see #register(Listener) + */ + void unregister(Listener listener); + + /** + * Interface for listening to events on {@link ThresholdSensor} + */ + interface Listener { + /** + * Called whenever the threshold for the registered sensor is crossed. + */ + void onThresholdCrossed(ThresholdSensorEvent event); + } + + /** + * Returned when the below/above state of a {@link ThresholdSensor} changes. + */ + class ThresholdSensorEvent { + private final boolean mBelow; + private final long mTimestampNs; + + public ThresholdSensorEvent(boolean below, long timestampNs) { + mBelow = below; + mTimestampNs = timestampNs; + } + + public boolean getBelow() { + return mBelow; + } + + public long getTimestampNs() { + return mTimestampNs; + } + + public long getTimestampMs() { + return mTimestampNs / 1000000; + } + + @Override + public String toString() { + return String.format((Locale) null, "{near=%s, timestamp_ns=%d}", mBelow, mTimestampNs); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java new file mode 100644 index 000000000000..546333b3a073 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ThresholdSensorImpl.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.sensors; + +import android.content.res.Resources; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.Assert; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +class ThresholdSensorImpl implements ThresholdSensor { + private static final String TAG = "ThresholdSensor"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final AsyncSensorManager mSensorManager; + private final Sensor mSensor; + private final float mThreshold; + private boolean mRegistered; + private boolean mPaused; + private List<Listener> mListeners = new ArrayList<>(); + private Boolean mLastBelow; + private String mTag; + private int mSensorDelay; + + private SensorEventListener mSensorEventListener = new SensorEventListener() { + @Override + public void onSensorChanged(SensorEvent event) { + boolean below = event.values[0] < mThreshold; + logDebug("Sensor value: " + event.values[0]); + onSensorEvent(below, event.timestamp); + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + }; + + private ThresholdSensorImpl(AsyncSensorManager sensorManager, + Sensor sensor, float threshold, int sensorDelay) { + mSensorManager = sensorManager; + mSensor = sensor; + mThreshold = threshold; + mSensorDelay = sensorDelay; + } + + @Override + public void setTag(String tag) { + mTag = tag; + } + + @Override + public void setDelay(int delay) { + if (delay == mSensorDelay) { + return; + } + + mSensorDelay = delay; + if (isLoaded()) { + unregisterInternal(); + registerInternal(); + } + } + + @Override + public boolean isLoaded() { + return mSensor != null; + } + + @VisibleForTesting + boolean isRegistered() { + return mRegistered; + } + + /** + * Registers the listener with the sensor. + * + * Multiple listeners are not supported at this time. + * + * Returns true if the listener was successfully registered. False otherwise. + */ + @Override + public void register(Listener listener) { + Assert.isMainThread(); + if (!mListeners.contains(listener)) { + mListeners.add(listener); + } + registerInternal(); + } + + @Override + public void unregister(Listener listener) { + Assert.isMainThread(); + mListeners.remove(listener); + unregisterInternal(); + } + + /** + * Unregister with the {@link SensorManager} without unsetting listeners on this object. + */ + @Override + public void pause() { + Assert.isMainThread(); + mPaused = true; + unregisterInternal(); + } + + /** + * Register with the {@link SensorManager}. No-op if no listeners are registered on this object. + */ + @Override + public void resume() { + Assert.isMainThread(); + mPaused = false; + registerInternal(); + } + + private void alertListenersInternal(boolean below, long timestampNs) { + List<Listener> listeners = new ArrayList<>(mListeners); + listeners.forEach(listener -> + listener.onThresholdCrossed(new ThresholdSensorEvent(below, timestampNs))); + } + + private void registerInternal() { + Assert.isMainThread(); + if (mRegistered || mPaused || mListeners.isEmpty()) { + return; + } + logDebug("Registering sensor listener"); + mSensorManager.registerListener(mSensorEventListener, mSensor, mSensorDelay); + mRegistered = true; + } + + private void unregisterInternal() { + Assert.isMainThread(); + if (!mRegistered) { + return; + } + logDebug("Unregister sensor listener"); + mSensorManager.unregisterListener(mSensorEventListener); + mRegistered = false; + mLastBelow = null; // Forget what we know. + } + + private void onSensorEvent(boolean below, long timestampNs) { + Assert.isMainThread(); + if (mLastBelow != null && mLastBelow == below) { + return; + } + mLastBelow = below; + alertListenersInternal(below, timestampNs); + } + + + @Override + public String toString() { + return String.format("{registered=%s, paused=%s, threshold=%s, sensor=%s}", + isLoaded(), mRegistered, mPaused, mThreshold, mSensor); + } + + private void logDebug(String msg) { + if (DEBUG) { + Log.d(TAG, (mTag != null ? "[" + mTag + "] " : "") + msg); + } + } + + static class Builder { + private final Resources mResources; + private final AsyncSensorManager mSensorManager; + private int mSensorDelay = SensorManager.SENSOR_DELAY_NORMAL;; + private float mThresholdValue; + private Sensor mSensor; + private boolean mSensorSet; + private boolean mThresholdSet; + + @Inject + Builder(@Main Resources resources, AsyncSensorManager sensorManager) { + mResources = resources; + mSensorManager = sensorManager; + } + + + Builder setSensorDelay(int sensorDelay) { + mSensorDelay = sensorDelay; + return this; + } + + Builder setSensorResourceId(int sensorResourceId) { + setSensorType(mResources.getString(sensorResourceId)); + return this; + } + + Builder setThresholdResourceId(int thresholdResourceId) { + try { + setThresholdValue(mResources.getFloat(thresholdResourceId)); + } catch (Resources.NotFoundException e) { + // no-op + } + return this; + } + + Builder setSensorType(String sensorType) { + Sensor sensor = findSensorByType(sensorType); + if (sensor != null) { + setSensor(sensor); + } + return this; + } + + Builder setThresholdValue(float thresholdValue) { + mThresholdValue = thresholdValue; + mThresholdSet = true; + return this; + } + + Builder setSensor(Sensor sensor) { + mSensor = sensor; + mSensorSet = true; + return this; + } + + /** + * Creates a {@link ThresholdSensor} backed by a {@link ThresholdSensorImpl}. + */ + public ThresholdSensor build() { + if (!mSensorSet) { + throw new IllegalStateException("A sensor was not successfully set."); + } + + if (!mThresholdSet) { + throw new IllegalStateException("A threshold was not successfully set."); + } + + return new ThresholdSensorImpl( + mSensorManager, mSensor, mThresholdValue, mSensorDelay); + } + + private Sensor findSensorByType(String sensorType) { + if (sensorType.isEmpty()) { + return null; + } + + List<Sensor> sensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL); + Sensor sensor = null; + for (Sensor s : sensorList) { + if (sensorType.equals(s.getStringType())) { + sensor = s; + break; + } + } + + return sensor; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java index 8b5cc9abb777..ffe062598102 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/BrightLineFalsingManagerTest.java @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.sensors.ThresholdSensor; import org.junit.Before; import org.junit.Test; @@ -77,7 +78,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { @Test public void testRegisterSensor() { mFalsingManager.onScreenTurningOn(); - verify(mProximitySensor).register(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).register(any(ThresholdSensor.Listener.class)); } @Test @@ -85,7 +86,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mFalsingManager.onScreenTurningOn(); reset(mProximitySensor); mFalsingManager.onScreenOff(); - verify(mProximitySensor).unregister(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class)); } @Test @@ -93,9 +94,9 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mFalsingManager.onScreenTurningOn(); reset(mProximitySensor); mFalsingManager.setQsExpanded(true); - verify(mProximitySensor).unregister(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class)); mFalsingManager.setQsExpanded(false); - verify(mProximitySensor).register(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).register(any(ThresholdSensor.Listener.class)); } @Test @@ -103,9 +104,9 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mFalsingManager.onScreenTurningOn(); reset(mProximitySensor); mFalsingManager.onBouncerShown(); - verify(mProximitySensor).unregister(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class)); mFalsingManager.onBouncerHidden(); - verify(mProximitySensor).register(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).register(any(ThresholdSensor.Listener.class)); } @Test @@ -113,6 +114,6 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mFalsingManager.onScreenTurningOn(); reset(mProximitySensor); mStatusBarStateController.setState(StatusBarState.SHADE); - verify(mProximitySensor).unregister(any(ProximitySensor.ProximitySensorListener.class)); + verify(mProximitySensor).unregister(any(ThresholdSensor.Listener.class)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java index 5b32a39403cd..3cebf0d6af57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ProximityClassifierTest.java @@ -136,7 +136,8 @@ public class ProximityClassifierTest extends ClassifierTest { motionEvent.recycle(); } - private ProximitySensor.ProximityEvent createSensorEvent(boolean covered, long timestampMs) { - return new ProximitySensor.ProximityEvent(covered, timestampMs * NS_PER_MS); + private ProximitySensor.ThresholdSensorEvent createSensorEvent( + boolean covered, long timestampMs) { + return new ProximitySensor.ThresholdSensorEvent(covered, timestampMs * NS_PER_MS); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java index 317500cf5b02..2208ac0dcbbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java @@ -46,6 +46,7 @@ import com.android.systemui.doze.DozeSensors.TriggerSensor; import com.android.systemui.plugins.SensorManagerPlugin; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.sensors.AsyncSensorManager; +import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.wakelock.WakeLock; import org.junit.Before; @@ -83,6 +84,9 @@ public class DozeSensorsTest extends SysuiTestCase { private DozeLog mDozeLog; @Mock private Sensor mProximitySensor; + @Mock + private ProximitySensor mMockProxSensor; + private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener; private TestableLooper mTestableLooper; private DozeSensors mDozeSensors; @@ -104,9 +108,9 @@ public class DozeSensorsTest extends SysuiTestCase { @Test public void testRegisterProx() { // We should not register with the sensor manager initially. - verify(mSensorManager, never()).registerListener(any(), any(Sensor.class), anyInt()); + verify(mMockProxSensor).pause(); mDozeSensors.setProxListening(true); - verify(mSensorManager).registerListener(any(), any(Sensor.class), anyInt()); + verify(mMockProxSensor).resume(); } @Test @@ -169,7 +173,8 @@ public class DozeSensorsTest extends SysuiTestCase { TestableDozeSensors() { super(getContext(), mAlarmManager, mSensorManager, mDozeParameters, - mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog); + mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog, + mMockProxSensor); for (TriggerSensor sensor : mSensors) { if (sensor instanceof PluginSensor && ((PluginSensor) sensor).mPluginSensor.getType() diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index debc9d6430e0..1d2021721634 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -42,10 +42,13 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.statusbar.phone.DozeParameters; +import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.FakeProximitySensor; import com.android.systemui.util.sensors.FakeSensorManager; +import com.android.systemui.util.sensors.FakeThresholdSensor; import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.util.wakelock.WakeLockFake; @@ -71,10 +74,12 @@ public class DozeTriggersTest extends SysuiTestCase { private BroadcastDispatcher mBroadcastDispatcher; @Mock private DockManager mDockManager; + private DozeTriggers mTriggers; private FakeSensorManager mSensors; private Sensor mTapSensor; private FakeProximitySensor mProximitySensor; + private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); @Before public void setUp() throws Exception { @@ -86,7 +91,10 @@ public class DozeTriggersTest extends SysuiTestCase { WakeLock wakeLock = new WakeLockFake(); AsyncSensorManager asyncSensorManager = new AsyncSensorManager(mSensors, null, new Handler()); - mProximitySensor = new FakeProximitySensor(getContext().getResources(), asyncSensorManager); + + FakeThresholdSensor thresholdSensor = new FakeThresholdSensor(); + thresholdSensor.setLoaded(true); + mProximitySensor = new FakeProximitySensor(thresholdSensor, null, mExecutor); mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, config, parameters, asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true, @@ -104,17 +112,17 @@ public class DozeTriggersTest extends SysuiTestCase { mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE); clearInvocations(mMachine); - mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(true, 1)); + mProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(true, 1)); captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */); mProximitySensor.alertListeners(); verify(mMachine, never()).requestState(any()); verify(mMachine, never()).requestPulse(anyInt()); - captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */); - waitForSensorManager(); - mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(false, 2)); + mProximitySensor.setLastEvent(new ProximitySensor.ThresholdSensorEvent(false, 2)); mProximitySensor.alertListeners(); + waitForSensorManager(); + captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */); verify(mMachine).requestPulse(anyInt()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java index 31d884c38f58..16edc9d9cf82 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeProximitySensor.java @@ -16,13 +16,15 @@ package com.android.systemui.util.sensors; -import android.content.res.Resources; +import com.android.systemui.util.concurrency.DelayableExecutor; public class FakeProximitySensor extends ProximitySensor { private boolean mAvailable; - public FakeProximitySensor(Resources resources, AsyncSensorManager sensorManager) { - super(resources, sensorManager); + public FakeProximitySensor(ThresholdSensor primary, ThresholdSensor secondary, + DelayableExecutor delayableExecutor) { + super(primary, secondary == null ? new FakeThresholdSensor() : secondary, + delayableExecutor); mAvailable = true; } @@ -30,12 +32,12 @@ public class FakeProximitySensor extends ProximitySensor { mAvailable = available; } - public void setLastEvent(ProximityEvent event) { + public void setLastEvent(ThresholdSensorEvent event) { mLastEvent = event; } @Override - public boolean getSensorAvailable() { + public boolean isLoaded() { return mAvailable; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java new file mode 100644 index 000000000000..d9f978944cde --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/FakeThresholdSensor.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.sensors; + +import java.util.ArrayList; +import java.util.List; + +public class FakeThresholdSensor implements ThresholdSensor { + private boolean mIsLoaded; + private boolean mPaused; + private List<Listener> mListeners = new ArrayList<>(); + + public FakeThresholdSensor() { + } + + public void setTag(String tag) { + } + + @Override + public void setDelay(int delay) { + } + + @Override + public boolean isLoaded() { + return mIsLoaded; + } + + @Override + public void pause() { + mPaused = true; + } + + @Override + public void resume() { + mPaused = false; + } + + @Override + public void register(ThresholdSensor.Listener listener) { + mListeners.add(listener); + } + + @Override + public void unregister(ThresholdSensor.Listener listener) { + mListeners.remove(listener); + } + + public void setLoaded(boolean loaded) { + mIsLoaded = loaded; + } + + void triggerEvent(boolean below, long timestampNs) { + if (!mPaused) { + for (Listener listener : mListeners) { + listener.onThresholdCrossed(new ThresholdSensorEvent(below, timestampNs)); + } + } + } + + boolean isPaused() { + return mPaused; + } + + int getNumListeners() { + return mListeners.size(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java new file mode 100644 index 000000000000..a39c20169518 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.sensors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ProximitySensorDualTest extends SysuiTestCase { + private ProximitySensor mProximitySensor; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + private FakeThresholdSensor mThresholdSensorPrimary; + private FakeThresholdSensor mThresholdSensorSecondary; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + allowTestableLooperAsMainThread(); + mThresholdSensorPrimary = new FakeThresholdSensor(); + mThresholdSensorPrimary.setLoaded(true); + mThresholdSensorSecondary = new FakeThresholdSensor(); + mThresholdSensorSecondary.setLoaded(true); + + mProximitySensor = new ProximitySensor( + mThresholdSensorPrimary, mThresholdSensorSecondary, mFakeExecutor); + } + + @Test + public void testSingleListener() { + TestableListener listener = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + mProximitySensor.register(listener); + assertTrue(mProximitySensor.isRegistered()); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger second sensor. Nothing should happen yet. + mThresholdSensorSecondary.triggerEvent(true, 0); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger first sensor. Our second sensor is now registered. + mThresholdSensorPrimary.triggerEvent(true, 0); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertFalse(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger second sensor. + mThresholdSensorSecondary.triggerEvent(true, 0); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + assertTrue(mThresholdSensorSecondary.isPaused()); + + mProximitySensor.unregister(listener); + } + + @Test + public void testSecondaryPausing() { + TestableListener listener = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + mProximitySensor.register(listener); + assertTrue(mProximitySensor.isRegistered()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger first sensor. Our second sensor is now registered. + mThresholdSensorPrimary.triggerEvent(true, 0); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger second sensor. + mThresholdSensorSecondary.triggerEvent(true, 0); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + assertTrue(mThresholdSensorSecondary.isPaused()); + + // Advance time. Second sensor should resume. + mFakeExecutor.advanceClockToNext(); + mFakeExecutor.runNextReady(); + assertFalse(mThresholdSensorSecondary.isPaused()); + + // Triggering should pause again. + mThresholdSensorSecondary.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); + assertTrue(mThresholdSensorSecondary.isPaused()); + + mProximitySensor.unregister(listener); + } + + @Test + public void testUnregister() { + TestableListener listener = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + mProximitySensor.register(listener); + assertTrue(mProximitySensor.isRegistered()); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + + mThresholdSensorPrimary.triggerEvent(true, 0); + mThresholdSensorSecondary.triggerEvent(true, 0); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + mProximitySensor.unregister(listener); + assertTrue(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertFalse(mProximitySensor.isRegistered()); + } + + @Test + public void testPauseAndResume() { + TestableListener listener = new TestableListener(); + + assertFalse(mProximitySensor.isRegistered()); + mProximitySensor.register(listener); + assertTrue(mProximitySensor.isRegistered()); + assertNull(listener.mLastEvent); + + mThresholdSensorPrimary.triggerEvent(true, 0); + mThresholdSensorSecondary.triggerEvent(true, 0); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + mProximitySensor.pause(); + assertFalse(mProximitySensor.isRegistered()); + assertTrue(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + + // More events do nothing when paused. + mThresholdSensorSecondary.triggerEvent(false, 1); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + mProximitySensor.resume(); + assertTrue(mProximitySensor.isRegistered()); + // Still matches our previous call + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + // Need to trigger the primary sensor before the secondary re-registers itself. + mThresholdSensorPrimary.triggerEvent(true, 3); + mThresholdSensorSecondary.triggerEvent(false, 3); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); + + mProximitySensor.unregister(listener); + assertFalse(mProximitySensor.isRegistered()); + } + + @Test + public void testPrimarySecondaryDisagreement() { + TestableListener listener = new TestableListener(); + + mProximitySensor.register(listener); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + // Trigger our sensors with different values. Secondary overrides primary. + mThresholdSensorPrimary.triggerEvent(true, 0); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + mThresholdSensorSecondary.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + mThresholdSensorSecondary.resume(); + mThresholdSensorSecondary.triggerEvent(true, 0); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); + + mThresholdSensorSecondary.resume(); + mThresholdSensorSecondary.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(3, listener.mCallCount); + + mProximitySensor.unregister(listener); + } + + @Test + public void testPrimaryCancelsSecondary() { + TestableListener listener = new TestableListener(); + + mProximitySensor.register(listener); + assertFalse(mThresholdSensorPrimary.isPaused()); + assertTrue(mThresholdSensorSecondary.isPaused()); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + + mThresholdSensorPrimary.triggerEvent(true, 0); + assertNull(listener.mLastEvent); + assertEquals(0, listener.mCallCount); + mThresholdSensorSecondary.triggerEvent(true, 0); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + + // When the primary reports false, the secondary is no longer needed. We get an immediate + // report. + mThresholdSensorPrimary.triggerEvent(false, 1); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); + + // The secondary is now ignored. No more work is scheduled. + mFakeExecutor.advanceClockToNext(); + mFakeExecutor.runNextReady(); + mThresholdSensorSecondary.triggerEvent(true, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); + assertEquals(0, mFakeExecutor.numPending()); + + mProximitySensor.unregister(listener); + } + + private static class TestableListener implements ThresholdSensor.Listener { + ThresholdSensor.ThresholdSensorEvent mLastEvent; + int mCallCount = 0; + + @Override + public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent proximityEvent) { + mLastEvent = proximityEvent; + mCallCount++; + } + }; + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java index 526fba726e9d..f1cee5061695 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorSingleTest.java @@ -21,33 +21,40 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +/** + * Tests for ProximitySensor that rely on a single hardware sensor. + */ @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -public class ProximitySensorTest extends SysuiTestCase { - +public class ProximitySensorSingleTest extends SysuiTestCase { private ProximitySensor mProximitySensor; - private FakeSensorManager.FakeProximitySensor mFakeProximitySensor; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + private FakeThresholdSensor mThresholdSensor; @Before public void setUp() throws Exception { - FakeSensorManager sensorManager = new FakeSensorManager(getContext()); - AsyncSensorManager asyncSensorManager = new AsyncSensorManager( - sensorManager, null, new Handler()); - mFakeProximitySensor = sensorManager.getFakeProximitySensor(); - mProximitySensor = new ProximitySensor(getContext().getResources(), asyncSensorManager); + MockitoAnnotations.initMocks(this); + allowTestableLooperAsMainThread(); + mThresholdSensor = new FakeThresholdSensor(); + mThresholdSensor.setLoaded(true); + + mProximitySensor = new ProximitySensor( + mThresholdSensor, new FakeThresholdSensor(), mFakeExecutor); } @Test @@ -56,19 +63,17 @@ public class ProximitySensorTest extends SysuiTestCase { assertFalse(mProximitySensor.isRegistered()); mProximitySensor.register(listener); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listener.mLastEvent); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); - mFakeProximitySensor.sendProximityResult(false); - assertTrue(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 2); + mThresholdSensor.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + mThresholdSensor.triggerEvent(true, 0); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); mProximitySensor.unregister(listener); - waitForSensorManager(); } @Test @@ -79,28 +84,25 @@ public class ProximitySensorTest extends SysuiTestCase { assertFalse(mProximitySensor.isRegistered()); mProximitySensor.register(listenerA); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); mProximitySensor.register(listenerB); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listenerA.mLastEvent); assertNull(listenerB.mLastEvent); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listenerA.mLastEvent.getNear()); - assertFalse(listenerB.mLastEvent.getNear()); - assertEquals(listenerA.mCallCount, 1); - assertEquals(listenerB.mCallCount, 1); - mFakeProximitySensor.sendProximityResult(false); - assertTrue(listenerA.mLastEvent.getNear()); - assertTrue(listenerB.mLastEvent.getNear()); - assertEquals(listenerA.mCallCount, 2); - assertEquals(listenerB.mCallCount, 2); + mThresholdSensor.triggerEvent(false, 0); + assertFalse(listenerA.mLastEvent.getBelow()); + assertFalse(listenerB.mLastEvent.getBelow()); + assertEquals(1, listenerA.mCallCount); + assertEquals(1, listenerB.mCallCount); + mThresholdSensor.triggerEvent(true, 1); + assertTrue(listenerA.mLastEvent.getBelow()); + assertTrue(listenerB.mLastEvent.getBelow()); + assertEquals(2, listenerA.mCallCount); + assertEquals(2, listenerB.mCallCount); mProximitySensor.unregister(listenerA); mProximitySensor.unregister(listenerB); - waitForSensorManager(); } @Test @@ -110,22 +112,19 @@ public class ProximitySensorTest extends SysuiTestCase { assertFalse(mProximitySensor.isRegistered()); mProximitySensor.register(listenerA); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); mProximitySensor.register(listenerA); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listenerA.mLastEvent); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listenerA.mLastEvent.getNear()); - assertEquals(listenerA.mCallCount, 1); - mFakeProximitySensor.sendProximityResult(false); - assertTrue(listenerA.mLastEvent.getNear()); - assertEquals(listenerA.mCallCount, 2); + mThresholdSensor.triggerEvent(false, 0); + assertFalse(listenerA.mLastEvent.getBelow()); + assertEquals(1, listenerA.mCallCount); + mThresholdSensor.triggerEvent(true, 1); + assertTrue(listenerA.mLastEvent.getBelow()); + assertEquals(2, listenerA.mCallCount); mProximitySensor.unregister(listenerA); - waitForSensorManager(); } @Test public void testUnregister() { @@ -133,16 +132,14 @@ public class ProximitySensorTest extends SysuiTestCase { assertFalse(mProximitySensor.isRegistered()); mProximitySensor.register(listener); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listener.mLastEvent); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); + mThresholdSensor.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); mProximitySensor.unregister(listener); - waitForSensorManager(); assertFalse(mProximitySensor.isRegistered()); } @@ -152,39 +149,35 @@ public class ProximitySensorTest extends SysuiTestCase { assertFalse(mProximitySensor.isRegistered()); mProximitySensor.register(listener); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listener.mLastEvent); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); + mThresholdSensor.triggerEvent(false, 0); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); mProximitySensor.pause(); - waitForSensorManager(); assertFalse(mProximitySensor.isRegistered()); // More events do nothing when paused. - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); - mFakeProximitySensor.sendProximityResult(false); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); + mThresholdSensor.triggerEvent(false, 1); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); + mThresholdSensor.triggerEvent(true, 2); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); mProximitySensor.resume(); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); // Still matches our previous call - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 1); + assertFalse(listener.mLastEvent.getBelow()); + assertEquals(1, listener.mCallCount); - mFakeProximitySensor.sendProximityResult(true); - assertFalse(listener.mLastEvent.getNear()); - assertEquals(listener.mCallCount, 2); + mThresholdSensor.triggerEvent(true, 3); + assertTrue(listener.mLastEvent.getBelow()); + assertEquals(2, listener.mCallCount); mProximitySensor.unregister(listener); - waitForSensorManager(); assertFalse(mProximitySensor.isRegistered()); } @@ -197,46 +190,35 @@ public class ProximitySensorTest extends SysuiTestCase { mProximitySensor.register(listenerA); mProximitySensor.register(listenerB); - waitForSensorManager(); assertTrue(mProximitySensor.isRegistered()); assertNull(listenerA.mLastEvent); assertNull(listenerB.mLastEvent); mProximitySensor.alertListeners(); assertNull(listenerA.mLastEvent); - assertEquals(listenerA.mCallCount, 1); + assertEquals(0, listenerA.mCallCount); assertNull(listenerB.mLastEvent); - assertEquals(listenerB.mCallCount, 1); + assertEquals(0, listenerB.mCallCount); - mFakeProximitySensor.sendProximityResult(false); - assertTrue(listenerA.mLastEvent.getNear()); - assertEquals(listenerA.mCallCount, 2); - assertTrue(listenerB.mLastEvent.getNear()); - assertEquals(listenerB.mCallCount, 2); + mThresholdSensor.triggerEvent(true, 0); + assertTrue(listenerA.mLastEvent.getBelow()); + assertEquals(1, listenerA.mCallCount); + assertTrue(listenerB.mLastEvent.getBelow()); + assertEquals(1, listenerB.mCallCount); mProximitySensor.unregister(listenerA); mProximitySensor.unregister(listenerB); - waitForSensorManager(); } - class TestableListener implements ProximitySensor.ProximitySensorListener { - ProximitySensor.ProximityEvent mLastEvent; + private static class TestableListener implements ThresholdSensor.Listener { + ThresholdSensor.ThresholdSensorEvent mLastEvent; int mCallCount = 0; @Override - public void onSensorEvent(ProximitySensor.ProximityEvent proximityEvent) { + public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent proximityEvent) { mLastEvent = proximityEvent; mCallCount++; } - - void reset() { - mLastEvent = null; - mCallCount = 0; - } }; - private void waitForSensorManager() { - TestableLooper.get(this).processAllMessages(); - } - } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java new file mode 100644 index 000000000000..09ec8e55efd6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ThresholdSensorImplTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.sensors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.os.Handler; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ThresholdSensorImplTest extends SysuiTestCase { + + private ThresholdSensorImpl mThresholdSensor; + private FakeSensorManager.FakeProximitySensor mFakeProximitySensor; + + @Before + public void setUp() throws Exception { + allowTestableLooperAsMainThread(); + FakeSensorManager sensorManager = new FakeSensorManager(getContext()); + + AsyncSensorManager asyncSensorManager = new AsyncSensorManager( + sensorManager, null, new Handler()); + + mFakeProximitySensor = sensorManager.getFakeProximitySensor(); + ThresholdSensorImpl.Builder thresholdSensorBuilder = new ThresholdSensorImpl.Builder( + null, asyncSensorManager); + mThresholdSensor = (ThresholdSensorImpl) thresholdSensorBuilder + .setSensor(mFakeProximitySensor.getSensor()) + .setThresholdValue(mFakeProximitySensor.getSensor().getMaximumRange()) + .build(); + } + + @Test + public void testSingleListener() { + TestableListener listener = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + mThresholdSensor.register(listener); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listener.mCallCount); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + mFakeProximitySensor.sendProximityResult(false); + assertTrue(listener.mBelow); + assertEquals(2, listener.mCallCount); + + mThresholdSensor.unregister(listener); + waitForSensorManager(); + } + + @Test + public void testMultiListener() { + TestableListener listenerA = new TestableListener(); + TestableListener listenerB = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + + mThresholdSensor.register(listenerA); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + mThresholdSensor.register(listenerB); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listenerA.mCallCount); + assertEquals(0, listenerB.mCallCount); + + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listenerA.mBelow); + assertFalse(listenerB.mBelow); + assertEquals(1, listenerA.mCallCount); + assertEquals(1, listenerB.mCallCount); + mFakeProximitySensor.sendProximityResult(false); + assertTrue(listenerA.mBelow); + assertTrue(listenerB.mBelow); + assertEquals(2, listenerA.mCallCount); + assertEquals(2, listenerB.mCallCount); + + mThresholdSensor.unregister(listenerA); + mThresholdSensor.unregister(listenerB); + waitForSensorManager(); + } + + @Test + public void testDuplicateListener() { + TestableListener listenerA = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + + mThresholdSensor.register(listenerA); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + mThresholdSensor.register(listenerA); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listenerA.mCallCount); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listenerA.mBelow); + assertEquals(1, listenerA.mCallCount); + mFakeProximitySensor.sendProximityResult(false); + assertTrue(listenerA.mBelow); + assertEquals(2, listenerA.mCallCount); + + mThresholdSensor.unregister(listenerA); + waitForSensorManager(); + } + @Test + public void testUnregister() { + TestableListener listener = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + mThresholdSensor.register(listener); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listener.mCallCount); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + + mThresholdSensor.unregister(listener); + waitForSensorManager(); + assertFalse(mThresholdSensor.isRegistered()); + } + + @Test + public void testPauseAndResume() { + TestableListener listener = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + mThresholdSensor.register(listener); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listener.mCallCount); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + + mThresholdSensor.pause(); + waitForSensorManager(); + assertFalse(mThresholdSensor.isRegistered()); + + // More events do nothing when paused. + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + mFakeProximitySensor.sendProximityResult(false); + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + + mThresholdSensor.resume(); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + // Still matches our previous call + assertFalse(listener.mBelow); + assertEquals(1, listener.mCallCount); + + mFakeProximitySensor.sendProximityResult(false); + assertTrue(listener.mBelow); + assertEquals(2, listener.mCallCount); + + mThresholdSensor.unregister(listener); + waitForSensorManager(); + assertFalse(mThresholdSensor.isRegistered()); + } + + @Test + public void testAlertListeners() { + TestableListener listenerA = new TestableListener(); + TestableListener listenerB = new TestableListener(); + + assertFalse(mThresholdSensor.isRegistered()); + + mThresholdSensor.register(listenerA); + mThresholdSensor.register(listenerB); + waitForSensorManager(); + assertTrue(mThresholdSensor.isRegistered()); + assertEquals(0, listenerA.mCallCount); + assertEquals(0, listenerB.mCallCount); + + mFakeProximitySensor.sendProximityResult(true); + assertFalse(listenerA.mBelow); + assertEquals(1, listenerA.mCallCount); + assertFalse(listenerB.mBelow); + assertEquals(1, listenerB.mCallCount); + + mFakeProximitySensor.sendProximityResult(false); + assertTrue(listenerA.mBelow); + assertEquals(2, listenerA.mCallCount); + assertTrue(listenerB.mBelow); + assertEquals(2, listenerB.mCallCount); + + mThresholdSensor.unregister(listenerA); + mThresholdSensor.unregister(listenerB); + waitForSensorManager(); + } + + static class TestableListener implements ThresholdSensor.Listener { + boolean mBelow; + long mTimestampNs; + int mCallCount; + + @Override + public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent event) { + mBelow = event.getBelow(); + mTimestampNs = event.getTimestampNs(); + mCallCount++; + } + } + + private void waitForSensorManager() { + TestableLooper.get(this).processAllMessages(); + } + +} diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk index dcdb80b497d0..1ffc8265f48a 100644 --- a/packages/overlays/Android.mk +++ b/packages/overlays/Android.mk @@ -47,7 +47,9 @@ LOCAL_REQUIRED_MODULES := \ IconPackRoundedThemePickerOverlay \ IconShapeRoundedRectOverlay \ IconShapeSquircleOverlay \ + IconShapeTaperedRectOverlay \ IconShapeTeardropOverlay \ + IconShapeVesselOverlay \ NavigationBarMode3ButtonOverlay \ NavigationBarModeGesturalOverlay \ NavigationBarModeGesturalOverlayNarrowBack \ diff --git a/packages/overlays/IconShapeHeartOverlay/AndroidManifest.xml b/packages/overlays/IconShapeHeartOverlay/AndroidManifest.xml index 82bff7d53d95..8fb19df33178 100644 --- a/packages/overlays/IconShapeHeartOverlay/AndroidManifest.xml +++ b/packages/overlays/IconShapeHeartOverlay/AndroidManifest.xml @@ -21,6 +21,7 @@ android:versionName="1.0"> <overlay android:targetPackage="android" + android:targetName="IconShapeCustomization" android:category="android.theme.customization.adaptive_icon_shape" android:priority="1"/> diff --git a/packages/overlays/IconShapeHexagonOverlay/AndroidManifest.xml b/packages/overlays/IconShapeHexagonOverlay/AndroidManifest.xml index bf408fd62569..e69de29bb2d1 100644 --- a/packages/overlays/IconShapeHexagonOverlay/AndroidManifest.xml +++ b/packages/overlays/IconShapeHexagonOverlay/AndroidManifest.xml @@ -1,27 +0,0 @@ -<!-- - ~ Copyright (C) 2020 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.theme.icon.hexagon" - android:versionCode="1" - android:versionName="1.0"> - <overlay - android:targetPackage="android" - android:category="android.theme.customization.adaptive_icon_shape" - android:priority="1"/> - - <application android:label="@string/icon_shape_hexagon_overlay" android:hasCode="false"/> -</manifest> diff --git a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml index d719a97e28f2..6842dde36264 100644 --- a/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml +++ b/packages/overlays/IconShapePebbleOverlay/AndroidManifest.xml @@ -21,6 +21,7 @@ android:versionName="1.0"> <overlay android:targetPackage="android" + android:targetName="IconShapeCustomization" android:category="android.theme.customization.adaptive_icon_shape" android:priority="1"/> diff --git a/packages/overlays/IconShapeHexagonOverlay/Android.mk b/packages/overlays/IconShapeTaperedRectOverlay/Android.mk index 16ef3995dedd..6f1bf2370a15 100644 --- a/packages/overlays/IconShapeHexagonOverlay/Android.mk +++ b/packages/overlays/IconShapeTaperedRectOverlay/Android.mk @@ -17,13 +17,13 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_RRO_THEME := IconShapeHexagon +LOCAL_RRO_THEME := IconShapeTaperedRect LOCAL_PRODUCT_MODULE := true LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_PACKAGE_NAME := IconShapeHexagonOverlay +LOCAL_PACKAGE_NAME := IconShapeTaperedRectOverlay LOCAL_SDK_VERSION := current include $(BUILD_RRO_PACKAGE) diff --git a/packages/overlays/IconShapeTaperedRectOverlay/AndroidManifest.xml b/packages/overlays/IconShapeTaperedRectOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..363e33ce450e --- /dev/null +++ b/packages/overlays/IconShapeTaperedRectOverlay/AndroidManifest.xml @@ -0,0 +1,28 @@ +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.theme.icon.taperedrect" + android:versionCode="1" + android:versionName="1.0"> + <overlay + android:targetPackage="android" + android:targetName="IconShapeCustomization" + android:category="android.theme.customization.adaptive_icon_shape" + android:priority="1"/> + + <application android:label="@string/icon_shape_tapered_rect_overlay" android:hasCode="false"/> +</manifest> diff --git a/packages/overlays/IconShapeHexagonOverlay/res/values/config.xml b/packages/overlays/IconShapeTaperedRectOverlay/res/values/config.xml index f7cb5951b4f1..ace06406c13c 100644 --- a/packages/overlays/IconShapeHexagonOverlay/res/values/config.xml +++ b/packages/overlays/IconShapeTaperedRectOverlay/res/values/config.xml @@ -16,7 +16,7 @@ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Specifies the path that is used by AdaptiveIconDrawable class to crop launcher icons. --> - <string name="config_icon_mask" translatable="false">"M12,0 88,0 100,50 88,100 12,100 0,50 12,0 Z"</string> + <string name="config_icon_mask" translatable="false">"M20,0 80,0 100,20 100,80 80,100 20,100 0,80 0,20 20,0 Z"</string> <!-- Flag indicating whether round icons should be parsed from the application manifest. --> <bool name="config_useRoundIcon">false</bool> <!-- Corner radius of system dialogs --> diff --git a/packages/overlays/IconShapeHexagonOverlay/res/values/strings.xml b/packages/overlays/IconShapeTaperedRectOverlay/res/values/strings.xml index e00dc9d938ec..3f36598a89bc 100644 --- a/packages/overlays/IconShapeHexagonOverlay/res/values/strings.xml +++ b/packages/overlays/IconShapeTaperedRectOverlay/res/values/strings.xml @@ -14,7 +14,7 @@ ~ limitations under the License. --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Hexagon icon overlay --> - <string name="icon_shape_hexagon_overlay" translatable="false">Hexagon</string> + <!-- Tapered rect icon overlay --> + <string name="icon_shape_tapered_rect_overlay" translatable="false">Tapered Rect</string> </resources> diff --git a/packages/overlays/IconShapeFlowerOverlay/Android.mk b/packages/overlays/IconShapeVesselOverlay/Android.mk index d410bb72b723..0816e6f7800d 100644 --- a/packages/overlays/IconShapeFlowerOverlay/Android.mk +++ b/packages/overlays/IconShapeVesselOverlay/Android.mk @@ -17,13 +17,13 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) -LOCAL_RRO_THEME := IconShapeFlower +LOCAL_RRO_THEME := IconShapeVessel LOCAL_PRODUCT_MODULE := true LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_PACKAGE_NAME := IconShapeFlowerOverlay +LOCAL_PACKAGE_NAME := IconShapeVesselOverlay LOCAL_SDK_VERSION := current include $(BUILD_RRO_PACKAGE) diff --git a/packages/overlays/IconShapeFlowerOverlay/AndroidManifest.xml b/packages/overlays/IconShapeVesselOverlay/AndroidManifest.xml index 9d20c6b6f82c..24f52dd99bd5 100644 --- a/packages/overlays/IconShapeFlowerOverlay/AndroidManifest.xml +++ b/packages/overlays/IconShapeVesselOverlay/AndroidManifest.xml @@ -14,13 +14,14 @@ ~ limitations under the License. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.theme.icon.flower" + package="com.android.theme.icon.vessel" android:versionCode="1" android:versionName="1.0"> <overlay android:targetPackage="android" + android:targetName="IconShapeCustomization" android:category="android.theme.customization.adaptive_icon_shape" android:priority="1"/> - <application android:label="@string/icon_shape_flower_overlay" android:hasCode="false"/> + <application android:label="@string/icon_shape_vessel_overlay" android:hasCode="false"/> </manifest> diff --git a/packages/overlays/IconShapeFlowerOverlay/res/values/config.xml b/packages/overlays/IconShapeVesselOverlay/res/values/config.xml index 73f4f2175a4b..86d31f6450bc 100644 --- a/packages/overlays/IconShapeFlowerOverlay/res/values/config.xml +++ b/packages/overlays/IconShapeVesselOverlay/res/values/config.xml @@ -16,7 +16,7 @@ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <!-- Specifies the path that is used by AdaptiveIconDrawable class to crop launcher icons. --> - <string name="config_icon_mask" translatable="false">"M50,0 C60.6,0 69.9,5.3 75.6,13.5 78.5,17.8 82.3,21.5 86.6,24.5 94.7,30.1 100,39.4 100,50 100,60.6 94.7,69.9 86.5,75.6 82.2,78.5 78.5,82.3 75.5,86.6 69.9,94.7 60.6,100 50,100 39.4,100 30.1,94.7 24.4,86.5 21.5,82.2 17.7,78.5 13.4,75.5 5.3,69.9 0,60.6 0,50 0,39.4 5.3,30.1 13.5,24.4 17.8,21.5 21.5,17.7 24.5,13.4 30.1,5.3 39.4,0 50,0 Z"</string> + <string name="config_icon_mask" translatable="false">"M12.97,0 C8.41,0 4.14,2.55 2.21,6.68 -1.03,13.61 -0.71,21.78 3.16,28.46 4.89,31.46 4.89,35.2 3.16,38.2 -1.05,45.48 -1.05,54.52 3.16,61.8 4.89,64.8 4.89,68.54 3.16,71.54 -0.71,78.22 -1.03,86.39 2.21,93.32 4.14,97.45 8.41,100 12.97,100 21.38,100 78.62,100 87.03,100 91.59,100 95.85,97.45 97.79,93.32 101.02,86.39 100.71,78.22 96.84,71.54 95.1,68.54 95.1,64.8 96.84,61.8 101.05,54.52 101.05,45.48 96.84,38.2 95.1,35.2 95.1,31.46 96.84,28.46 100.71,21.78 101.02,13.61 97.79,6.68 95.85,2.55 91.59,0 87.03,0 78.62,0 21.38,0 12.97,0 Z"</string> <!-- Flag indicating whether round icons should be parsed from the application manifest. --> <bool name="config_useRoundIcon">false</bool> <!-- Corner radius of system dialogs --> diff --git a/packages/overlays/IconShapeFlowerOverlay/res/values/strings.xml b/packages/overlays/IconShapeVesselOverlay/res/values/strings.xml index 47c1479e5a40..a50e7e9a9ab2 100644 --- a/packages/overlays/IconShapeFlowerOverlay/res/values/strings.xml +++ b/packages/overlays/IconShapeVesselOverlay/res/values/strings.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <!-- Flower icon overlay --> - <string name="icon_shape_flower_overlay" translatable="false">Flower</string> + <!-- Vessel icon overlay --> + <string name="icon_shape_vessel_overlay" translatable="false">Vessel</string> </resources> |