Create a basic doze dream in SystemUI.
The doze dream is not configured by default.
When configured, the doze dream does not show anything by default.
It teases a dark version of the keyguard (showing only the time
and notifications) when a notification arrives or significant motion
is detected.
Bug:15863249
Change-Id: Icfceb054d35d6fd4d9178eda7480e2464873ca4b
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
new file mode 100644
index 0000000..cc0d4a7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2014 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.doze;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
+import android.os.PowerManager;
+import android.os.Vibrator;
+import android.service.dreams.DozeHardware;
+import android.service.dreams.DreamService;
+import android.util.Log;
+
+import com.android.systemui.SystemUIApplication;
+
+public class DozeService extends DreamService {
+ private static final boolean DEBUG = false;
+
+ private static final String TEASE_ACTION = "com.android.systemui.doze.tease";
+
+ private final String mTag = String.format("DozeService.%08x", hashCode());
+ private final Context mContext = this;
+
+ private Host mHost;
+ private DozeHardware mDozeHardware;
+ private SensorManager mSensors;
+ private Sensor mSigMotionSensor;
+ private PowerManager mPowerManager;
+ private PowerManager.WakeLock mWakeLock;
+ private boolean mDreaming;
+ private boolean mTeaseReceiverRegistered;
+
+ public DozeService() {
+ if (DEBUG) Log.d(mTag, "new DozeService()");
+ setDebug(DEBUG);
+ }
+
+ @Override
+ public void onCreate() {
+ if (DEBUG) Log.d(mTag, "onCreate");
+ super.onCreate();
+
+ if (getApplication() instanceof SystemUIApplication) {
+ final SystemUIApplication app = (SystemUIApplication) getApplication();
+ mHost = app.getComponent(Host.class);
+ }
+
+ setWindowless(true);
+
+ mSensors = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+ mSigMotionSensor = mSensors.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ if (DEBUG) Log.d(mTag, "onAttachedToWindow");
+ super.onAttachedToWindow();
+ }
+
+ @Override
+ public void onDreamingStarted() {
+ super.onDreamingStarted();
+ mDozeHardware = getDozeHardware();
+ if (DEBUG) Log.d(mTag, "onDreamingStarted canDoze=" + canDoze()
+ + " dozeHardware=" + mDozeHardware);
+ mDreaming = true;
+ listenForTeaseSignals(true);
+ requestDoze();
+ }
+
+ public void stayAwake(long millis) {
+ if (mDreaming && millis > 0) {
+ mWakeLock.acquire(millis);
+ }
+ }
+
+ public void startDozing() {
+ if (DEBUG) Log.d(mTag, "startDozing mDreaming=" + mDreaming);
+ if (!mDreaming) {
+ Log.w(mTag, "Not dozing, no longer dreaming");
+ return;
+ }
+
+ super.startDozing();
+ }
+
+ @Override
+ public void onDreamingStopped() {
+ if (DEBUG) Log.d(mTag, "onDreamingStopped isDozing=" + isDozing());
+ super.onDreamingStopped();
+
+ mDreaming = false;
+ mDozeHardware = null;
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ listenForTeaseSignals(false);
+ stopDozing();
+ dozingStopped();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
+ super.onDetachedFromWindow();
+
+ dozingStopped();
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Log.d(mTag, "onDestroy");
+ super.onDestroy();
+
+ dozingStopped();
+ }
+
+ private void requestDoze() {
+ if (mHost != null) {
+ mHost.requestDoze(this);
+ }
+ }
+
+ private void requestTease() {
+ if (mHost != null) {
+ mHost.requestTease(this);
+ }
+ }
+
+ private void dozingStopped() {
+ if (mHost != null) {
+ mHost.dozingStopped(this);
+ }
+ }
+
+ private void listenForTeaseSignals(boolean listen) {
+ if (DEBUG) Log.d(mTag, "listenForTeaseSignals: " + listen);
+ if (mHost == null) return;
+ listenForSignificantMotion(listen);
+ if (listen) {
+ mContext.registerReceiver(mTeaseReceiver, new IntentFilter(TEASE_ACTION));
+ mTeaseReceiverRegistered = true;
+ mHost.addCallback(mHostCallback);
+ } else {
+ if (mTeaseReceiverRegistered) {
+ mContext.unregisterReceiver(mTeaseReceiver);
+ }
+ mTeaseReceiverRegistered = false;
+ mHost.removeCallback(mHostCallback);
+ }
+ }
+
+ private void listenForSignificantMotion(boolean listen) {
+ if (mSigMotionSensor == null) return;
+ if (listen) {
+ mSensors.requestTriggerSensor(mSigMotionListener, mSigMotionSensor);
+ } else {
+ mSensors.cancelTriggerSensor(mSigMotionListener, mSigMotionSensor);
+ }
+ }
+
+ private static String triggerEventToString(TriggerEvent event) {
+ if (event == null) return null;
+ final StringBuilder sb = new StringBuilder("TriggerEvent[")
+ .append(event.timestamp).append(',')
+ .append(event.sensor.getName());
+ if (event.values != null) {
+ for (int i = 0; i < event.values.length; i++) {
+ sb.append(',').append(event.values[i]);
+ }
+ }
+ return sb.append(']').toString();
+ }
+
+ private final TriggerEventListener mSigMotionListener = new TriggerEventListener() {
+ @Override
+ public void onTrigger(TriggerEvent event) {
+ if (DEBUG) Log.d(mTag, "sigMotion.onTrigger: " + triggerEventToString(event));
+ if (DEBUG) {
+ final Vibrator v = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ if (v != null) {
+ v.vibrate(1000);
+ }
+ }
+ requestTease();
+ listenForSignificantMotion(true); // reregister, this sensor only fires once
+ }
+ };
+
+ private final BroadcastReceiver mTeaseReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Log.d(mTag, "Received tease intent");
+ requestTease();
+ }
+ };
+
+ private final Host.Callback mHostCallback = new Host.Callback() {
+ @Override
+ public void onNewNotifications() {
+ if (DEBUG) Log.d(mTag, "onNewNotifications");
+ requestTease();
+ }
+ };
+
+ public interface Host {
+ void addCallback(Callback callback);
+ void removeCallback(Callback callback);
+ void requestDoze(DozeService dozeService);
+ void requestTease(DozeService dozeService);
+ void dozingStopped(DozeService dozeService);
+
+ public interface Callback {
+ void onNewNotifications();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index f6f78e9..1550217 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -25,6 +25,8 @@
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -89,6 +91,8 @@
= new PathInterpolator(0, 0, 0.5f, 1);
private boolean mDimmed;
+ private boolean mDark;
+ private final Paint mDarkPaint = createDarkPaint();
private int mBgResId = com.android.internal.R.drawable.notification_material_bg;
private int mDimmedBgResId = com.android.internal.R.drawable.notification_material_bg_dim;
@@ -295,6 +299,34 @@
}
}
+ public void setDark(boolean dark, boolean fade) {
+ // TODO implement fade
+ if (mDark != dark) {
+ mDark = dark;
+ if (mDark) {
+ setLayerType(View.LAYER_TYPE_HARDWARE, mDarkPaint);
+ } else {
+ setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+ }
+
+ private static Paint createDarkPaint() {
+ final Paint p = new Paint();
+ final float[] invert = {
+ -1f, 0f, 0f, 1f, 1f,
+ 0f, -1f, 0f, 1f, 1f,
+ 0f, 0f, -1f, 1f, 1f,
+ 0f, 0f, 0f, 1f, 0f
+ };
+ final ColorMatrix m = new ColorMatrix(invert);
+ final ColorMatrix grayscale = new ColorMatrix();
+ grayscale.setSaturation(0);
+ m.preConcat(grayscale);
+ p.setColorFilter(new ColorMatrixColorFilter(m));
+ return p;
+ }
+
/**
* Sets the resource id for the background of this notification.
*
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 4d4a8ab..c3fb83c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -167,6 +167,15 @@
}
/**
+ * Sets the notification as dark. The default implementation does nothing.
+ *
+ * @param dark Whether the notification should be dark.
+ * @param fade Whether an animation should be played to change the state.
+ */
+ public void setDark(boolean dark, boolean fade) {
+ }
+
+ /**
* @return The desired notification height.
*/
public int getIntrinsicHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 00951b2..714bedf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -100,6 +100,7 @@
import com.android.systemui.DemoMode;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
+import com.android.systemui.doze.DozeService;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.qs.CircularClipper;
import com.android.systemui.qs.QSPanel;
@@ -225,6 +226,7 @@
private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
private StatusBarWindowManager mStatusBarWindowManager;
private UnlockMethodCache mUnlockMethodCache;
+ private DozeServiceHost mDozeServiceHost;
int mPixelFormat;
Object mQueueLock = new Object();
@@ -405,6 +407,7 @@
private boolean mSettingsClosing;
private boolean mVisible;
private boolean mWaitingForKeyguardExit;
+ private boolean mDozing;
private Interpolator mLinearOutSlowIn;
private Interpolator mAlphaOut = new PathInterpolator(0f, 0.4f, 1f, 1f);
@@ -428,6 +431,8 @@
private final ArraySet<String> mCurrentlyVisibleNotifications = new ArraySet<String>();
private long mLastVisibilityReportUptimeMs;
+ private final ShadeUpdates mShadeUpdates = new ShadeUpdates();
+
private static final int VISIBLE_LOCATIONS = ViewState.LOCATION_FIRST_CARD
| ViewState.LOCATION_TOP_STACK_PEEKING
| ViewState.LOCATION_MAIN_AREA
@@ -537,6 +542,9 @@
}
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
startKeyguard();
+
+ mDozeServiceHost = new DozeServiceHost();
+ putComponent(DozeService.Host.class, mDozeServiceHost);
}
// ================================================================================
@@ -1232,7 +1240,6 @@
for (View remove : toRemove) {
mStackScroller.removeView(remove);
}
-
for (int i=0; i<toShow.size(); i++) {
View v = toShow.get(i);
if (v.getParent() == null) {
@@ -1265,6 +1272,7 @@
updateRowStates();
updateSpeedbump();
mNotificationPanel.setQsExpansionEnabled(provisioned && mUserSetup);
+ mShadeUpdates.check();
}
private void updateSpeedbump() {
@@ -2273,6 +2281,7 @@
pw.println(windowStateToString(mStatusBarWindowState));
pw.print(" mStatusBarMode=");
pw.println(BarTransitions.modeToString(mStatusBarMode));
+ pw.print(" mDozing="); pw.println(mDozing);
pw.print(" mZenMode=");
pw.println(Settings.Global.zenModeToString(mZenMode));
pw.print(" mUseHeadsUp=");
@@ -2675,7 +2684,6 @@
if (newlyVisible.isEmpty() && noLongerVisible.isEmpty()) {
return;
}
-
String[] newlyVisibleAr = newlyVisible.toArray(new String[newlyVisible.size()]);
String[] noLongerVisibleAr = noLongerVisible.toArray(new String[noLongerVisible.size()]);
try {
@@ -2950,6 +2958,7 @@
mNotificationPanel.setKeyguardShowing(false);
mScrimController.setKeyguardShowing(false);
}
+ updateDozingState();
updateStackScrollerState();
updatePublicMode();
updateNotifications();
@@ -2957,6 +2966,26 @@
updateCarrierLabelVisibility(false);
}
+ private void updateDozingState() {
+ final boolean bottomGone = mKeyguardBottomArea.getVisibility() == View.GONE;
+ if (mDozing) {
+ mNotificationPanel.setBackgroundColor(0xff000000);
+ mHeader.setVisibility(View.INVISIBLE);
+ if (!bottomGone) {
+ mKeyguardBottomArea.setVisibility(View.INVISIBLE);
+ }
+ mStackScroller.setDark(true, false /*animate*/);
+ } else {
+ mNotificationPanel.setBackground(null);
+ mHeader.setVisibility(View.VISIBLE);
+ if (!bottomGone) {
+ mKeyguardBottomArea.setVisibility(View.VISIBLE);
+ }
+ mStackScroller.setDark(false, false /*animate*/);
+ }
+ mScrimController.setDozing(mDozing);
+ }
+
public void updateStackScrollerState() {
if (mStackScroller == null) return;
boolean onKeyguard = mState == StatusBarState.KEYGUARD;
@@ -3229,4 +3258,121 @@
}
notifyUiVisibilityChanged(mSystemUiVisibility);
}
+
+ private final class ShadeUpdates {
+ private final ArraySet<String> mVisibleNotifications = new ArraySet<String>();
+ private final ArraySet<String> mNewVisibleNotifications = new ArraySet<String>();
+
+ public void check() {
+ mNewVisibleNotifications.clear();
+ for (int i = 0; i < mNotificationData.size(); i++) {
+ final Entry entry = mNotificationData.get(i);
+ final boolean visible = entry.row != null
+ && entry.row.getVisibility() == View.VISIBLE;
+ if (visible) {
+ mNewVisibleNotifications.add(entry.key + entry.notification.getPostTime());
+ }
+ }
+ final boolean updates = !mVisibleNotifications.containsAll(mNewVisibleNotifications);
+ mVisibleNotifications.clear();
+ mVisibleNotifications.putAll(mNewVisibleNotifications);
+
+ // We have new notifications
+ if (updates && mDozeServiceHost != null) {
+ mDozeServiceHost.fireNewNotifications();
+ }
+ }
+ }
+
+ private final class DozeServiceHost implements DozeService.Host {
+ // Amount of time to allow to update the time shown on the screen before releasing
+ // the wakelock. This timeout is design to compensate for the fact that we don't
+ // currently have a way to know when time display contents have actually been
+ // refreshed once we've finished rendering a new frame.
+ private static final long PROCESSING_TIME = 500;
+
+ private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final H mHandler = new H();
+
+ private DozeService mCurrentDozeService;
+
+ public void fireNewNotifications() {
+ for (Callback callback : mCallbacks) {
+ callback.onNewNotifications();
+ }
+ }
+
+ @Override
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ @Override
+ public void requestDoze(DozeService dozeService) {
+ if (dozeService == null) return;
+ dozeService.stayAwake(PROCESSING_TIME);
+ mHandler.obtainMessage(H.REQUEST_DOZE, dozeService).sendToTarget();
+ }
+
+ @Override
+ public void requestTease(DozeService dozeService) {
+ if (dozeService == null) return;
+ dozeService.stayAwake(PROCESSING_TIME);
+ mHandler.obtainMessage(H.REQUEST_TEASE, dozeService).sendToTarget();
+ }
+
+ @Override
+ public void dozingStopped(DozeService dozeService) {
+ if (dozeService == null) return;
+ dozeService.stayAwake(PROCESSING_TIME);
+ mHandler.obtainMessage(H.DOZING_STOPPED, dozeService).sendToTarget();
+ }
+
+ private void handleRequestDoze(DozeService dozeService) {
+ mCurrentDozeService = dozeService;
+ if (!mDozing) {
+ mDozing = true;
+ updateDozingState();
+ }
+ mCurrentDozeService.startDozing();
+ }
+
+ private void handleRequestTease(DozeService dozeService) {
+ if (!dozeService.equals(mCurrentDozeService)) return;
+ final long stayAwake = mScrimController.tease();
+ mCurrentDozeService.stayAwake(stayAwake);
+ }
+
+ private void handleDozingStopped(DozeService dozeService) {
+ if (dozeService.equals(mCurrentDozeService)) {
+ mCurrentDozeService = null;
+ }
+ if (mDozing) {
+ mDozing = false;
+ updateDozingState();
+ }
+ }
+
+ private final class H extends Handler {
+ private static final int REQUEST_DOZE = 1;
+ private static final int REQUEST_TEASE = 2;
+ private static final int DOZING_STOPPED = 3;
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == REQUEST_DOZE) {
+ handleRequestDoze((DozeService) msg.obj);
+ } else if (msg.what == REQUEST_TEASE) {
+ handleRequestTease((DozeService) msg.obj);
+ } else if (msg.what == DOZING_STOPPED) {
+ handleDozingStopped((DozeService) msg.obj);
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 1264d75..bf63f7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -21,21 +21,35 @@
import android.animation.ValueAnimator;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
+import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import com.android.systemui.R;
+
/**
* Controls both the scrim behind the notifications and in front of the notifications (when a
* security method gets shown).
*/
public class ScrimController implements ViewTreeObserver.OnPreDrawListener {
+ private static final String TAG = "ScrimController";
+ private static final boolean DEBUG = false;
private static final float SCRIM_BEHIND_ALPHA = 0.62f;
private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.5f;
private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
private static final long ANIMATION_DURATION = 220;
+ private static final int TAG_KEY_ANIM = R.id.scrim;
+
+ private static final int NUM_TEASES = 3;
+ private static final long TEASE_IN_ANIMATION_DURATION = 500;
+ private static final long TEASE_VISIBLE_DURATION = 3000;
+ private static final long TEASE_OUT_ANIMATION_DURATION = 1000;
+ private static final long TEASE_INVISIBLE_DURATION = 1000;
+ private static final long TEASE_DURATION = TEASE_IN_ANIMATION_DURATION
+ + TEASE_VISIBLE_DURATION + TEASE_OUT_ANIMATION_DURATION + TEASE_INVISIBLE_DURATION;
private final View mScrimBehind;
private final View mScrimInFront;
@@ -54,6 +68,8 @@
private long mAnimationDelay;
private Runnable mOnAnimationFinished;
private boolean mAnimationStarted;
+ private boolean mDozing;
+ private int mTeasesRemaining;
private final Interpolator mInterpolator = new DecelerateInterpolator();
@@ -97,6 +113,29 @@
scheduleUpdate();
}
+ public void setDozing(boolean dozing) {
+ if (mDozing == dozing) return;
+ mDozing = dozing;
+ if (!mDozing) {
+ cancelTeasing();
+ }
+ scheduleUpdate();
+ }
+
+ /** When dozing, fade screen contents in and out a few times using the front scrim. */
+ public long tease() {
+ if (!mDozing) return 0;
+ mTeasesRemaining = NUM_TEASES;
+ mScrimInFront.post(mTeaseIn);
+ return NUM_TEASES * TEASE_DURATION;
+ }
+
+ private void cancelTeasing() {
+ mTeasesRemaining = 0;
+ mScrimInFront.removeCallbacks(mTeaseIn);
+ mScrimInFront.removeCallbacks(mTeaseOut);
+ }
+
private void scheduleUpdate() {
if (mUpdatePending) return;
@@ -125,6 +164,8 @@
} else if (mBouncerShowing) {
setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
setScrimBehindColor(0f);
+ } else if (mDozing) {
+ setScrimInFrontColor(1);
} else {
setScrimInFrontColor(0f);
setScrimBehindColor(SCRIM_BEHIND_ALPHA_KEYGUARD);
@@ -174,6 +215,10 @@
if (current == targetColor) {
return;
}
+ Object runningAnim = scrim.getTag(TAG_KEY_ANIM);
+ if (runningAnim instanceof ValueAnimator) {
+ ((ValueAnimator) runningAnim).cancel();
+ }
ValueAnimator anim = ValueAnimator.ofInt(current, target);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
@@ -193,9 +238,11 @@
mOnAnimationFinished.run();
mOnAnimationFinished = null;
}
+ scrim.setTag(TAG_KEY_ANIM, null);
}
});
anim.start();
+ scrim.setTag(TAG_KEY_ANIM, anim);
mAnimationStarted = true;
}
@@ -225,4 +272,51 @@
mAnimationStarted = false;
return true;
}
+
+ private final Runnable mTeaseIn = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Tease in, mDozing=" + mDozing
+ + " mTeasesRemaining=" + mTeasesRemaining);
+ if (!mDozing || mTeasesRemaining == 0) return;
+ mTeasesRemaining--;
+ mDurationOverride = TEASE_IN_ANIMATION_DURATION;
+ mAnimationDelay = 0;
+ mAnimateChange = true;
+ mOnAnimationFinished = mTeaseInFinished;
+ setScrimColor(mScrimInFront, 0);
+ }
+ };
+
+ private final Runnable mTeaseInFinished = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Tease in finished, mDozing=" + mDozing);
+ if (!mDozing) return;
+ mScrimInFront.postDelayed(mTeaseOut, TEASE_VISIBLE_DURATION);
+ }
+ };
+
+ private final Runnable mTeaseOut = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Tease in finished, mDozing=" + mDozing);
+ if (!mDozing) return;
+ mDurationOverride = TEASE_OUT_ANIMATION_DURATION;
+ mAnimationDelay = 0;
+ mAnimateChange = true;
+ mOnAnimationFinished = mTeaseOutFinished;
+ setScrimColor(mScrimInFront, 1);
+ }
+ };
+
+ private final Runnable mTeaseOutFinished = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) Log.d(TAG, "Tease out finished, mTeasesRemaining=" + mTeasesRemaining);
+ if (mTeasesRemaining > 0) {
+ mScrimInFront.postDelayed(mTeaseIn, TEASE_INVISIBLE_DURATION);
+ }
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index fcc951e..0582140 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -33,6 +33,7 @@
private float mOverScrollBottomAmount;
private int mSpeedBumpIndex = -1;
private float mScrimAmount;
+ private boolean mDark;
public int getScrollY() {
return mScrollY;
@@ -62,6 +63,11 @@
mDimmed = dimmed;
}
+ /** In dark mode, we draw as little as possible, assuming a black background */
+ public void setDark(boolean dark) {
+ mDark = dark;
+ }
+
/**
* In dimmed mode, a child can be activated, which happens on the first tap of the double-tap
* interaction. This child is then scaled normally and its background is fully opaque.
@@ -74,6 +80,10 @@
return mDimmed;
}
+ public boolean isDark() {
+ return mDark;
+ }
+
public ActivatableNotificationView getActivatedChild() {
return mActivatedChild;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
index cf56fa57..99d3a01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -29,6 +29,7 @@
boolean animateHeight;
boolean animateTopInset;
boolean animateDimmed;
+ boolean animateDark;
boolean hasDelays;
public AnimationFilter animateAlpha() {
@@ -71,6 +72,11 @@
return this;
}
+ public AnimationFilter animateDark() {
+ animateDark = true;
+ return this;
+ }
+
/**
* Combines multiple filters into {@code this} filter, using or as the operand .
*
@@ -92,6 +98,7 @@
animateHeight |= filter.animateHeight;
animateTopInset |= filter.animateTopInset;
animateDimmed |= filter.animateDimmed;
+ animateDark |= filter.animateDark;
hasDelays |= filter.hasDelays;
}
@@ -103,6 +110,7 @@
animateHeight = false;
animateTopInset = false;
animateDimmed = false;
+ animateDark = false;
hasDelays = false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index f6e9aef..4220efe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -128,6 +128,7 @@
private boolean mNeedsAnimation;
private boolean mTopPaddingNeedsAnimation;
private boolean mDimmedNeedsAnimation;
+ private boolean mDarkNeedsAnimation;
private boolean mActivateNeedsAnimation;
private boolean mIsExpanded = true;
private boolean mChildrenUpdateRequested;
@@ -356,14 +357,6 @@
return getNotGoneChildCount() > 1;
}
- private boolean isViewExpanded(View view) {
- if (view != null) {
- ExpandableView expandView = (ExpandableView) view;
- return expandView.getActualHeight() > mCollapsedSize;
- }
- return false;
- }
-
/**
* Updates the children views according to the stack scroll algorithm. Call this whenever
* modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
@@ -1479,6 +1472,7 @@
generateTopPaddingEvent();
generateActivateEvent();
generateDimmedEvent();
+ generateDarkEvent();
mNeedsAnimation = false;
}
@@ -1554,6 +1548,14 @@
mDimmedNeedsAnimation = false;
}
+ private void generateDarkEvent() {
+ if (mDarkNeedsAnimation) {
+ mAnimationEvents.add(
+ new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK));
+ }
+ mDarkNeedsAnimation = false;
+ }
+
private boolean onInterceptTouchEventScroll(MotionEvent ev) {
if (!isScrollingEnabled()) {
return false;
@@ -1852,6 +1854,18 @@
}
/**
+ * See {@link AmbientState#setDark}.
+ */
+ public void setDark(boolean dark, boolean animate) {
+ mAmbientState.setDark(dark);
+ if (animate && mAnimationsEnabled) {
+ mDarkNeedsAnimation = true;
+ mNeedsAnimation = true;
+ }
+ requestChildrenUpdate();
+ }
+
+ /**
* A listener that is notified when some child locations might have changed.
*/
public interface OnChildLocationsChangedListener {
@@ -1940,7 +1954,11 @@
.animateHeight()
.animateTopInset()
.animateY()
- .animateZ()
+ .animateZ(),
+
+ // ANIMATION_TYPE_DARK
+ new AnimationFilter()
+ .animateDark(),
};
static int[] LENGTHS = new int[] {
@@ -1971,6 +1989,9 @@
// ANIMATION_TYPE_CHANGE_POSITION
StackStateAnimator.ANIMATION_DURATION_STANDARD,
+
+ // ANIMATION_TYPE_DARK
+ StackStateAnimator.ANIMATION_DURATION_STANDARD,
};
static final int ANIMATION_TYPE_ADD = 0;
@@ -1982,6 +2003,7 @@
static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
static final int ANIMATION_TYPE_DIMMED = 7;
static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
+ static final int ANIMATION_TYPE_DARK = 9;
final long eventStartTime;
final View changingView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 9a4b798..4956fe8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -233,12 +233,14 @@
private void updateDimmedActivated(AmbientState ambientState, StackScrollState resultState,
StackScrollAlgorithmState algorithmState) {
boolean dimmed = ambientState.isDimmed();
+ boolean dark = ambientState.isDark();
View activatedChild = ambientState.getActivatedChild();
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
View child = algorithmState.visibleChildren.get(i);
StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
childViewState.dimmed = dimmed;
+ childViewState.dark = dark;
boolean isActivatedChild = activatedChild == child;
childViewState.scale = !dimmed || isActivatedChild
? 1.0f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 02f2cd6..d8407d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -148,6 +148,9 @@
// apply dimming
child.setDimmed(state.dimmed, false /* animate */);
+ // apply dark
+ child.setDark(state.dark, false /* animate */);
+
// apply scrimming
child.setScrimAmount(state.scrimAmount);
@@ -224,6 +227,7 @@
boolean gone;
float scale;
boolean dimmed;
+ boolean dark;
/**
* A value between 0 and 1 indicating how much the view should be scrimmed.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 0006dad..5efbc99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -176,6 +176,9 @@
// start dimmed animation
child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
+ // start dark animation
+ child.setDark(viewState.dark, mAnimationFilter.animateDark);
+
// apply scrimming
child.setScrimAmount(viewState.scrimAmount);