1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar; 18 19 import android.animation.ObjectAnimator; 20 import android.animation.ValueAnimator; 21 import android.text.format.DateFormat; 22 import android.util.FloatProperty; 23 import android.view.animation.Interpolator; 24 25 import com.android.internal.annotations.GuardedBy; 26 import com.android.systemui.Dumpable; 27 import com.android.systemui.Interpolators; 28 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; 29 import com.android.systemui.statusbar.notification.stack.StackStateAnimator; 30 import com.android.systemui.statusbar.policy.CallbackController; 31 32 import java.io.FileDescriptor; 33 import java.io.PrintWriter; 34 import java.util.ArrayList; 35 import java.util.Comparator; 36 37 import javax.inject.Inject; 38 import javax.inject.Singleton; 39 40 /** 41 * Tracks and reports on {@link StatusBarState}. 42 */ 43 @Singleton 44 public class StatusBarStateControllerImpl implements SysuiStatusBarStateController, 45 CallbackController<StateListener>, Dumpable { 46 private static final String TAG = "SbStateController"; 47 // Must be a power of 2 48 private static final int HISTORY_SIZE = 32; 49 50 private static final int MAX_STATE = StatusBarState.FULLSCREEN_USER_SWITCHER; 51 private static final int MIN_STATE = StatusBarState.SHADE; 52 53 private static final Comparator<RankedListener> sComparator = 54 Comparator.comparingInt(o -> o.mRank); 55 private static final FloatProperty<StatusBarStateControllerImpl> SET_DARK_AMOUNT_PROPERTY = 56 new FloatProperty<StatusBarStateControllerImpl>("mDozeAmount") { 57 58 @Override 59 public void setValue(StatusBarStateControllerImpl object, float value) { 60 object.setDozeAmountInternal(value); 61 } 62 63 @Override 64 public Float get(StatusBarStateControllerImpl object) { 65 return object.mDozeAmount; 66 } 67 }; 68 69 private final ArrayList<RankedListener> mListeners = new ArrayList<>(); 70 private int mState; 71 private int mLastState; 72 private boolean mLeaveOpenOnKeyguardHide; 73 private boolean mKeyguardRequested; 74 75 // Record the HISTORY_SIZE most recent states 76 private int mHistoryIndex = 0; 77 private HistoricalState[] mHistoricalRecords = new HistoricalState[HISTORY_SIZE]; 78 79 /** 80 * If the device is currently dozing or not. 81 */ 82 private boolean mIsDozing; 83 84 /** 85 * Current {@link #mDozeAmount} animator. 86 */ 87 private ValueAnimator mDarkAnimator; 88 89 /** 90 * Current doze amount in this frame. 91 */ 92 private float mDozeAmount; 93 94 /** 95 * Where the animator will stop. 96 */ 97 private float mDozeAmountTarget; 98 99 /** 100 * The type of interpolator that should be used to the doze animation. 101 */ 102 private Interpolator mDozeInterpolator = Interpolators.FAST_OUT_SLOW_IN; 103 104 @Inject StatusBarStateControllerImpl()105 public StatusBarStateControllerImpl() { 106 for (int i = 0; i < HISTORY_SIZE; i++) { 107 mHistoricalRecords[i] = new HistoricalState(); 108 } 109 } 110 111 @Override getState()112 public int getState() { 113 return mState; 114 } 115 116 @Override setState(int state)117 public boolean setState(int state) { 118 if (state > MAX_STATE || state < MIN_STATE) { 119 throw new IllegalArgumentException("Invalid state " + state); 120 } 121 if (state == mState) { 122 return false; 123 } 124 125 // Record the to-be mState and mLastState 126 recordHistoricalState(state, mState); 127 128 synchronized (mListeners) { 129 for (RankedListener rl : new ArrayList<>(mListeners)) { 130 rl.mListener.onStatePreChange(mState, state); 131 } 132 mLastState = mState; 133 mState = state; 134 for (RankedListener rl : new ArrayList<>(mListeners)) { 135 rl.mListener.onStateChanged(mState); 136 } 137 138 for (RankedListener rl : new ArrayList<>(mListeners)) { 139 rl.mListener.onStatePostChange(); 140 } 141 } 142 143 return true; 144 } 145 146 @Override isDozing()147 public boolean isDozing() { 148 return mIsDozing; 149 } 150 151 @Override getDozeAmount()152 public float getDozeAmount() { 153 return mDozeAmount; 154 } 155 156 @Override getInterpolatedDozeAmount()157 public float getInterpolatedDozeAmount() { 158 return mDozeInterpolator.getInterpolation(mDozeAmount); 159 } 160 161 @Override setIsDozing(boolean isDozing)162 public boolean setIsDozing(boolean isDozing) { 163 if (mIsDozing == isDozing) { 164 return false; 165 } 166 167 mIsDozing = isDozing; 168 169 synchronized (mListeners) { 170 for (RankedListener rl : new ArrayList<>(mListeners)) { 171 rl.mListener.onDozingChanged(isDozing); 172 } 173 } 174 175 return true; 176 } 177 178 @Override setDozeAmount(float dozeAmount, boolean animated)179 public void setDozeAmount(float dozeAmount, boolean animated) { 180 if (mDarkAnimator != null && mDarkAnimator.isRunning()) { 181 if (animated && mDozeAmountTarget == dozeAmount) { 182 return; 183 } else { 184 mDarkAnimator.cancel(); 185 } 186 } 187 188 mDozeAmountTarget = dozeAmount; 189 if (animated) { 190 startDozeAnimation(); 191 } else { 192 setDozeAmountInternal(dozeAmount); 193 } 194 } 195 startDozeAnimation()196 private void startDozeAnimation() { 197 if (mDozeAmount == 0f || mDozeAmount == 1f) { 198 mDozeInterpolator = mIsDozing 199 ? Interpolators.FAST_OUT_SLOW_IN 200 : Interpolators.TOUCH_RESPONSE_REVERSE; 201 } 202 mDarkAnimator = ObjectAnimator.ofFloat(this, SET_DARK_AMOUNT_PROPERTY, mDozeAmountTarget); 203 mDarkAnimator.setInterpolator(Interpolators.LINEAR); 204 mDarkAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_WAKEUP); 205 mDarkAnimator.start(); 206 } 207 setDozeAmountInternal(float dozeAmount)208 private void setDozeAmountInternal(float dozeAmount) { 209 mDozeAmount = dozeAmount; 210 float interpolatedAmount = mDozeInterpolator.getInterpolation(dozeAmount); 211 synchronized (mListeners) { 212 for (RankedListener rl : new ArrayList<>(mListeners)) { 213 rl.mListener.onDozeAmountChanged(mDozeAmount, interpolatedAmount); 214 } 215 } 216 } 217 218 @Override goingToFullShade()219 public boolean goingToFullShade() { 220 return mState == StatusBarState.SHADE && mLeaveOpenOnKeyguardHide; 221 } 222 223 @Override setLeaveOpenOnKeyguardHide(boolean leaveOpen)224 public void setLeaveOpenOnKeyguardHide(boolean leaveOpen) { 225 mLeaveOpenOnKeyguardHide = leaveOpen; 226 } 227 228 @Override leaveOpenOnKeyguardHide()229 public boolean leaveOpenOnKeyguardHide() { 230 return mLeaveOpenOnKeyguardHide; 231 } 232 233 @Override fromShadeLocked()234 public boolean fromShadeLocked() { 235 return mLastState == StatusBarState.SHADE_LOCKED; 236 } 237 238 @Override addCallback(StateListener listener)239 public void addCallback(StateListener listener) { 240 synchronized (mListeners) { 241 addListenerInternalLocked(listener, Integer.MAX_VALUE); 242 } 243 } 244 245 /** 246 * Add a listener and a rank based on the priority of this message 247 * @param listener the listener 248 * @param rank the order in which you'd like to be called. Ranked listeners will be 249 * notified before unranked, and we will sort ranked listeners from low to high 250 * 251 * @deprecated This method exists only to solve latent inter-dependencies from refactoring 252 * StatusBarState out of StatusBar.java. Any new listeners should be built not to need ranking 253 * (i.e., they are non-dependent on the order of operations of StatusBarState listeners). 254 */ 255 @Deprecated 256 @Override addCallback(StateListener listener, @SbStateListenerRank int rank)257 public void addCallback(StateListener listener, @SbStateListenerRank int rank) { 258 synchronized (mListeners) { 259 addListenerInternalLocked(listener, rank); 260 } 261 } 262 263 @GuardedBy("mListeners") addListenerInternalLocked(StateListener listener, int rank)264 private void addListenerInternalLocked(StateListener listener, int rank) { 265 // Protect against double-subscribe 266 for (RankedListener rl : mListeners) { 267 if (rl.mListener.equals(listener)) { 268 return; 269 } 270 } 271 272 RankedListener rl = new SysuiStatusBarStateController.RankedListener(listener, rank); 273 mListeners.add(rl); 274 mListeners.sort(sComparator); 275 } 276 277 278 @Override removeCallback(StateListener listener)279 public void removeCallback(StateListener listener) { 280 synchronized (mListeners) { 281 mListeners.removeIf((it) -> it.mListener.equals(listener)); 282 } 283 } 284 285 @Override setKeyguardRequested(boolean keyguardRequested)286 public void setKeyguardRequested(boolean keyguardRequested) { 287 mKeyguardRequested = keyguardRequested; 288 } 289 290 @Override isKeyguardRequested()291 public boolean isKeyguardRequested() { 292 return mKeyguardRequested; 293 } 294 295 /** 296 * Returns String readable state of status bar from {@link StatusBarState} 297 */ describe(int state)298 public static String describe(int state) { 299 return StatusBarState.toShortString(state); 300 } 301 302 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)303 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 304 pw.println("StatusBarStateController: "); 305 pw.println(" mState=" + mState + " (" + describe(mState) + ")"); 306 pw.println(" mLastState=" + mLastState + " (" + describe(mLastState) + ")"); 307 pw.println(" mLeaveOpenOnKeyguardHide=" + mLeaveOpenOnKeyguardHide); 308 pw.println(" mKeyguardRequested=" + mKeyguardRequested); 309 pw.println(" mIsDozing=" + mIsDozing); 310 pw.println(" Historical states:"); 311 // Ignore records without a timestamp 312 int size = 0; 313 for (int i = 0; i < HISTORY_SIZE; i++) { 314 if (mHistoricalRecords[i].mTimestamp != 0) size++; 315 } 316 for (int i = mHistoryIndex + HISTORY_SIZE; 317 i >= mHistoryIndex + HISTORY_SIZE - size + 1; i--) { 318 pw.println(" (" + (mHistoryIndex + HISTORY_SIZE - i + 1) + ")" 319 + mHistoricalRecords[i & (HISTORY_SIZE - 1)]); 320 } 321 } 322 recordHistoricalState(int currentState, int lastState)323 private void recordHistoricalState(int currentState, int lastState) { 324 mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; 325 HistoricalState state = mHistoricalRecords[mHistoryIndex]; 326 state.mState = currentState; 327 state.mLastState = lastState; 328 state.mTimestamp = System.currentTimeMillis(); 329 } 330 331 /** 332 * For keeping track of our previous state to help with debugging 333 */ 334 private static class HistoricalState { 335 int mState; 336 int mLastState; 337 long mTimestamp; 338 339 @Override toString()340 public String toString() { 341 if (mTimestamp != 0) { 342 StringBuilder sb = new StringBuilder(); 343 sb.append("state=").append(mState) 344 .append(" (").append(describe(mState)).append(")"); 345 sb.append("lastState=").append(mLastState).append(" (").append(describe(mLastState)) 346 .append(")"); 347 sb.append("timestamp=") 348 .append(DateFormat.format("MM-dd HH:mm:ss", mTimestamp)); 349 350 return sb.toString(); 351 } 352 return "Empty " + getClass().getSimpleName(); 353 } 354 } 355 } 356