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