AOD: Add wakelock for charging text while dozing

Also refactors the WakeLocks in SystemUI.

Bug: 30876804
Bug: 35850304
Test: runtest systemui
Change-Id: Ie17eedfd266deb3aa46dabd701bc784330b2e030
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index e38f922c..ba8e54a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -22,12 +22,12 @@
 import android.content.Context;
 import android.hardware.SensorManager;
 import android.os.Handler;
-import android.os.PowerManager;
 
 import com.android.internal.hardware.AmbientDisplayConfiguration;
 import com.android.systemui.SystemUIApplication;
 import com.android.systemui.plugins.doze.DozeProvider;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.wakelock.WakeLock;
 
 public class DozeFactory {
 
@@ -41,15 +41,13 @@
     public DozeMachine assembleMachine(DozeService dozeService) {
         Context context = dozeService;
         SensorManager sensorManager = context.getSystemService(SensorManager.class);
-        PowerManager powerManager = context.getSystemService(PowerManager.class);
         AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
 
         DozeHost host = getHost(dozeService);
         AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context);
         DozeParameters params = new DozeParameters(context);
         Handler handler = new Handler();
-        DozeFactory.WakeLock wakeLock = new DozeFactory.WakeLock(powerManager.newWakeLock(
-                PowerManager.PARTIAL_WAKE_LOCK, "Doze"));
+        WakeLock wakeLock = WakeLock.createPartial(context, "Doze");
 
         DozeMachine machine = new DozeMachine(
                 DozeScreenStatePreventingAdapter.wrapIfNeeded(dozeService, params),
@@ -157,28 +155,4 @@
         final SystemUIApplication app = (SystemUIApplication) appCandidate;
         return app.getComponent(DozeHost.class);
     }
-
-    /** A wrapper around {@link PowerManager.WakeLock} for testability. */
-    public static class WakeLock implements DozeProvider.WakeLock {
-        private final PowerManager.WakeLock mInner;
-
-        public WakeLock(PowerManager.WakeLock inner) {
-            mInner = inner;
-        }
-
-        /** @see PowerManager.WakeLock#acquire() */
-        public void acquire() {
-            mInner.acquire();
-        }
-
-        /** @see PowerManager.WakeLock#release() */
-        public void release() {
-            mInner.release();
-        }
-
-        /** @see PowerManager.WakeLock#wrap(Runnable) */
-        public Runnable wrap(Runnable runnable) {
-            return mInner.wrap(runnable);
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index c852b49..f27521e 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -24,6 +24,7 @@
 import com.android.internal.hardware.AmbientDisplayConfiguration;
 import com.android.internal.util.Preconditions;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.wakelock.WakeLock;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -95,7 +96,7 @@
     }
 
     private final Service mDozeService;
-    private final DozeFactory.WakeLock mWakeLock;
+    private final WakeLock mWakeLock;
     private final AmbientDisplayConfiguration mConfig;
     private Part[] mParts;
 
@@ -104,7 +105,7 @@
     private boolean mWakeLockHeldForCurrentState = false;
 
     public DozeMachine(Service service, AmbientDisplayConfiguration config,
-            DozeFactory.WakeLock wakeLock) {
+            WakeLock wakeLock) {
         mDozeService = service;
         mConfig = config;
         mWakeLock = wakeLock;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 9cc927d..2ac0657 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -36,6 +36,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.util.wakelock.WakeLock;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -53,14 +54,14 @@
     private final TriggerSensor mPickupSensor;
     private final DozeParameters mDozeParameters;
     private final AmbientDisplayConfiguration mConfig;
-    private final DozeFactory.WakeLock mWakeLock;
+    private final WakeLock mWakeLock;
     private final Callback mCallback;
 
     private final Handler mHandler = new Handler();
 
 
     public DozeSensors(Context context, SensorManager sensorManager, DozeParameters dozeParameters,
-            AmbientDisplayConfiguration config, DozeFactory.WakeLock wakeLock, Callback callback) {
+            AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback) {
         mContext = context;
         mSensorManager = sensorManager;
         mDozeParameters = dozeParameters;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index b5c7dd3..1b9bf73 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -36,6 +36,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.wakelock.WakeLock;
 
 import java.io.PrintWriter;
 
@@ -57,7 +58,7 @@
     private final DozeParameters mDozeParameters;
     private final SensorManager mSensorManager;
     private final Handler mHandler;
-    private final DozeFactory.WakeLock mWakeLock;
+    private final WakeLock mWakeLock;
     private final boolean mAllowPulseTriggers;
     private final UiModeManager mUiModeManager;
     private final TriggerReceiver mBroadcastReceiver = new TriggerReceiver();
@@ -69,7 +70,7 @@
     public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
             AmbientDisplayConfiguration config,
             DozeParameters dozeParameters, SensorManager sensorManager, Handler handler,
-            DozeFactory.WakeLock wakeLock, boolean allowPulseTriggers) {
+            WakeLock wakeLock, boolean allowPulseTriggers) {
         mContext = context;
         mMachine = machine;
         mDozeHost = dozeHost;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 76e0283..f577654 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -21,6 +21,8 @@
 import android.os.Handler;
 import android.os.SystemClock;
 
+import com.android.systemui.util.wakelock.WakeLock;
+
 import java.util.Calendar;
 import java.util.GregorianCalendar;
 
@@ -33,14 +35,14 @@
     private final AlarmManager mAlarmManager;
     private final DozeHost mHost;
     private final Handler mHandler;
-    private final DozeFactory.WakeLock mWakeLock;
+    private final WakeLock mWakeLock;
     private final DozeMachine mMachine;
     private final AlarmManager.OnAlarmListener mTimeTick;
 
     private boolean mTimeTickScheduled = false;
 
     public DozeUi(Context context, AlarmManager alarmManager, DozeMachine machine,
-            DozeFactory.WakeLock wakeLock, DozeHost host, Handler handler) {
+            WakeLock wakeLock, DozeHost host, Handler handler) {
         mContext = context;
         mAlarmManager = alarmManager;
         mMachine = machine;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 850e55e..dceeb74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -29,6 +29,7 @@
 import android.os.BatteryStats;
 import android.os.Handler;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -39,6 +40,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -49,6 +51,8 @@
 import com.android.systemui.statusbar.phone.LockIcon;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.util.wakelock.SettableWakeLock;
+import com.android.systemui.util.wakelock.WakeLock;
 
 /**
  * Controls the indications and error messages shown on the Keyguard
@@ -68,6 +72,7 @@
     private final KeyguardIndicationTextView mDisclosure;
     private final UserManager mUserManager;
     private final IBatteryStats mBatteryInfo;
+    private final SettableWakeLock mWakeLock;
 
     private final int mSlowThreshold;
     private final int mFastThreshold;
@@ -92,6 +97,13 @@
 
     public KeyguardIndicationController(Context context, ViewGroup indicationArea,
             LockIcon lockIcon) {
+        this(context, indicationArea, lockIcon,
+                WakeLock.createPartial(context, "Doze:KeyguardIndication"));
+    }
+
+    @VisibleForTesting
+    KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon,
+                WakeLock wakeLock) {
         mContext = context;
         mIndicationArea = indicationArea;
         mTextView = (KeyguardIndicationTextView) indicationArea.findViewById(
@@ -99,6 +111,7 @@
         mDisclosure = (KeyguardIndicationTextView) indicationArea.findViewById(
                 R.id.keyguard_indication_enterprise_disclosure);
         mLockIcon = lockIcon;
+        mWakeLock = new SettableWakeLock(wakeLock);
 
         Resources res = context.getResources();
         mSlowThreshold = res.getInteger(R.integer.config_chargingSlowlyThreshold);
@@ -208,6 +221,11 @@
         mTransientIndication = transientIndication;
         mTransientTextColor = textColor;
         mHandler.removeMessages(MSG_HIDE_TRANSIENT);
+        if (mDozing && !TextUtils.isEmpty(mTransientIndication)) {
+            // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared.
+            mWakeLock.setAcquired(true);
+            hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
+        }
         updateIndication();
     }
 
@@ -223,6 +241,10 @@
     }
 
     private void updateIndication() {
+        if (TextUtils.isEmpty(mTransientIndication)) {
+            mWakeLock.setAcquired(false);
+        }
+
         if (mVisible) {
             // Walk down a precedence-ordered list of what should indication
             // should be shown based on user or device state
@@ -323,9 +345,8 @@
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
-            if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) {
-                mTransientIndication = null;
-                updateIndication();
+            if (msg.what == MSG_HIDE_TRANSIENT) {
+                hideTransientIndication();
             } else if (msg.what == MSG_CLEAR_FP_MSG) {
                 mLockIcon.setTransientFpError(false);
                 hideTransientIndication();
@@ -353,9 +374,13 @@
             mChargingWattage = status.maxChargingWattage;
             mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold);
             updateIndication();
-            if (!wasPluggedIn && mPowerPluggedIn && mDozing) {
-                showTransientIndication(computePowerIndication());
-                hideTransientIndicationDelayed(HIDE_DELAY_MS);
+            if (mDozing) {
+                if (!wasPluggedIn && mPowerPluggedIn) {
+                    showTransientIndication(computePowerIndication());
+                    hideTransientIndicationDelayed(HIDE_DELAY_MS);
+                } else if (wasPluggedIn && !mPowerPluggedIn) {
+                    hideTransientIndication();
+                }
             }
         }
 
@@ -407,7 +432,6 @@
             } else if (updateMonitor.isDeviceInteractive()) {
                 showTransientIndication(errString, errorColor);
                 // We want to keep this message around in case the screen was off
-                mHandler.removeMessages(MSG_HIDE_TRANSIENT);
                 hideTransientIndicationDelayed(HIDE_DELAY_MS);
             } else {
                 mMessageToShowOnScreenOn = errString;
@@ -421,7 +445,6 @@
                 int errorColor = Utils.getColorError(mContext);
                 showTransientIndication(mMessageToShowOnScreenOn, errorColor);
                 // We want to keep this message around in case the screen was off
-                mHandler.removeMessages(MSG_HIDE_TRANSIENT);
                 hideTransientIndicationDelayed(HIDE_DELAY_MS);
                 mMessageToShowOnScreenOn = null;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/SettableWakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/SettableWakeLock.java
new file mode 100644
index 0000000..f2ed55f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/SettableWakeLock.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 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.wakelock;
+
+import android.os.Handler;
+import android.os.PowerManager;
+
+import com.android.internal.util.Preconditions;
+
+public class SettableWakeLock {
+
+    private final WakeLock mInner;
+
+    private boolean mAcquired;
+
+    public SettableWakeLock(WakeLock inner) {
+        Preconditions.checkNotNull(inner, "inner wakelock required");
+
+        mInner = inner;
+    }
+
+    public synchronized boolean isAcquired() {
+        return mAcquired;
+    }
+
+    public synchronized void setAcquired(boolean acquired) {
+        if (mAcquired != acquired) {
+            if (acquired) {
+                mInner.acquire();
+            } else {
+                mInner.release();
+            }
+            mAcquired = acquired;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
new file mode 100644
index 0000000..eea3de3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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.wakelock;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.support.annotation.VisibleForTesting;
+
+import com.android.systemui.plugins.doze.DozeProvider;
+
+/** WakeLock wrapper for testability */
+public interface WakeLock extends DozeProvider.WakeLock {
+
+    static WakeLock createPartial(Context context, String tag) {
+        return wrap(createPartialInner(context, tag));
+    }
+
+    @VisibleForTesting
+    static PowerManager.WakeLock createPartialInner(Context context, String tag) {
+        return context.getSystemService(PowerManager.class)
+                    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag);
+    }
+
+    static WakeLock wrap(final PowerManager.WakeLock inner) {
+        return new WakeLock() {
+            /** @see PowerManager.WakeLock#acquire() */
+            public void acquire() {
+                inner.acquire();
+            }
+
+            /** @see PowerManager.WakeLock#release() */
+            public void release() {
+                inner.release();
+            }
+
+            /** @see PowerManager.WakeLock#wrap(Runnable) */
+            public Runnable wrap(Runnable runnable) {
+                return inner.wrap(runnable);
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 41b75ff..612a54a 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -36,6 +36,7 @@
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
     <uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
     <uses-permission android:name="android.permission.CONTROL_VPN" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 3abd955..ba39671 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.SysUIRunner;
 import com.android.systemui.UiThreadTest;
 import com.android.internal.hardware.AmbientDisplayConfiguration;
+import com.android.systemui.util.wakelock.WakeLockFake;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 7335af3..6424a0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -16,19 +16,26 @@
 
 package com.android.systemui.statusbar;
 
+import static android.support.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.Instrumentation;
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.Context;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.internal.runner.junit4.statement.UiThreadStatement;
 import android.support.test.runner.AndroidJUnit4;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -36,6 +43,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
+import com.android.systemui.util.wakelock.WakeLockFake;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -54,9 +62,13 @@
     private KeyguardIndicationTextView mDisclosure = mock(KeyguardIndicationTextView.class);
 
     private KeyguardIndicationController mController;
+    private WakeLockFake mWakeLock;
+    private Instrumentation mInstrumentation;
 
     @Before
     public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
         mContext.addMockSystemService(Context.DEVICE_POLICY_SERVICE, mDevicePolicyManager);
         mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
         mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
@@ -65,13 +77,15 @@
 
         when(mIndicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure))
                 .thenReturn(mDisclosure);
+
+        mWakeLock = new WakeLockFake();
     }
 
     private void createController() {
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
-        mController = new KeyguardIndicationController(mContext, mIndicationArea, null);
+        mController = new KeyguardIndicationController(mContext, mIndicationArea, null, mWakeLock);
     }
 
     @Test
@@ -139,4 +153,45 @@
         verify(mDisclosure).setVisibility(View.GONE);
         verifyNoMoreInteractions(mDisclosure);
     }
+
+    @Test
+    public void transientIndication_holdsWakeLock_whenDozing() {
+        createController();
+
+        mController.setDozing(true);
+        mController.showTransientIndication("Test");
+
+        assertTrue(mWakeLock.isHeld());
+    }
+
+    @Test
+    public void transientIndication_releasesWakeLock_afterHiding() {
+        createController();
+
+        mController.setDozing(true);
+        mController.showTransientIndication("Test");
+        mController.hideTransientIndication();
+
+        assertFalse(mWakeLock.isHeld());
+    }
+
+    @Test
+    public void transientIndication_releasesWakeLock_afterHidingDelayed() throws Throwable {
+        mInstrumentation.runOnMainSync(() -> {
+            createController();
+
+            mController.setDozing(true);
+            mController.showTransientIndication("Test");
+            mController.hideTransientIndicationDelayed(0);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        boolean[] held = new boolean[2];
+        mInstrumentation.runOnMainSync(() -> {
+            held[0] = mWakeLock.isHeld();
+            held[1] = true;
+        });
+        assertFalse("wake lock still held", held[0]);
+        assertTrue("held was not written yet", held[1]);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/SettableWakeLockTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/SettableWakeLockTest.java
new file mode 100644
index 0000000..f6692eb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/SettableWakeLockTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 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.wakelock;
+
+import static junit.framework.TestCase.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SettableWakeLockTest {
+
+    private WakeLockFake mFake;
+    private SettableWakeLock mSettable;
+
+    @Before
+    public void setup() {
+        mFake = new WakeLockFake();
+        mSettable = new SettableWakeLock(mFake);
+    }
+
+    @Test
+    public void setAcquire_true_acquires() throws Exception {
+        mSettable.setAcquired(true);
+        assertTrue(mFake.isHeld());
+        assertEquals(mFake.isHeld(), mSettable.isAcquired());
+    }
+
+    @Test
+    public void setAcquire_false_releases() throws Exception {
+        mSettable.setAcquired(true);
+        mSettable.setAcquired(false);
+        assertFalse(mFake.isHeld());
+        assertEquals(mFake.isHeld(), mSettable.isAcquired());
+    }
+
+    @Test
+    public void setAcquire_true_multipleTimes_isIdempotent() throws Exception {
+        mSettable.setAcquired(true);
+        mSettable.setAcquired(true);
+        mSettable.setAcquired(true);
+        mSettable.setAcquired(false);
+        assertFalse(mFake.isHeld());
+        assertEquals(mFake.isHeld(), mSettable.isAcquired());
+    }
+
+    @Test
+    public void setAcquire_false_multipleTimes_idempotent() throws Exception {
+        mSettable.setAcquired(true);
+        mSettable.setAcquired(false);
+        mSettable.setAcquired(false);
+        assertFalse(mFake.isHeld());
+        assertEquals(mFake.isHeld(), mSettable.isAcquired());
+    }
+
+    @Test
+    public void setAcquire_false_multipleTimes_idempotent_again() throws Exception {
+        mSettable.setAcquired(true);
+        mSettable.setAcquired(false);
+        mSettable.setAcquired(false);
+        mSettable.setAcquired(true);
+        assertTrue(mFake.isHeld());
+        assertEquals(mFake.isHeld(), mSettable.isAcquired());
+    }
+
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/WakeLockFake.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java
similarity index 82%
rename from packages/SystemUI/tests/src/com/android/systemui/doze/WakeLockFake.java
rename to packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java
index 7c04fe2..4cefb99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/WakeLockFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockFake.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -11,21 +11,17 @@
  * 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.
+ * limitations under the License
  */
 
-package com.android.systemui.doze;
+package com.android.systemui.util.wakelock;
 
 import com.android.internal.util.Preconditions;
 
-public class WakeLockFake extends DozeFactory.WakeLock {
+public class WakeLockFake implements WakeLock {
 
     private int mAcquired = 0;
 
-    public WakeLockFake() {
-        super(null);
-    }
-
     @Override
     public void acquire() {
         mAcquired++;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
new file mode 100644
index 0000000..5394499
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 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.wakelock;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.PowerManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WakeLockTest {
+
+    WakeLock mWakeLock;
+    PowerManager.WakeLock mInner;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getContext();
+
+        mInner = WakeLock.createPartialInner(context, WakeLockTest.class.getName());
+        mWakeLock = WakeLock.wrap(mInner);
+    }
+
+    @After
+    public void tearDown() {
+        mInner.setReferenceCounted(false);
+        mInner.release();
+    }
+
+    @Test
+    public void createPartialInner_notHeldYet() {
+        assertFalse(mInner.isHeld());
+    }
+
+    @Test
+    public void wakeLock_acquire() {
+        mWakeLock.acquire();
+        assertTrue(mInner.isHeld());
+    }
+
+    @Test
+    public void wakeLock_release() {
+        mWakeLock.acquire();
+        mWakeLock.release();
+        assertFalse(mInner.isHeld());
+    }
+
+    @Test
+    public void wakeLock_refCounted() {
+        mWakeLock.acquire();
+        mWakeLock.acquire();
+        mWakeLock.release();
+        assertTrue(mInner.isHeld());
+    }
+
+    @Test
+    public void wakeLock_wrap() {
+        boolean[] ran = new boolean[1];
+
+        Runnable wrapped = mWakeLock.wrap(() -> {
+            ran[0] = true;
+        });
+
+        assertTrue(mInner.isHeld());
+        assertFalse(ran[0]);
+
+        wrapped.run();
+
+        assertTrue(ran[0]);
+        assertFalse(mInner.isHeld());
+    }
+}
\ No newline at end of file