1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.qs.tileimpl; 16 17 import static androidx.lifecycle.Lifecycle.State.CREATED; 18 import static androidx.lifecycle.Lifecycle.State.DESTROYED; 19 import static androidx.lifecycle.Lifecycle.State.RESUMED; 20 import static androidx.lifecycle.Lifecycle.State.STARTED; 21 22 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK; 23 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS; 24 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK; 25 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_IS_FULL_QS; 26 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION; 27 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE; 28 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_BAR_STATE; 29 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION; 30 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 31 32 import android.annotation.CallSuper; 33 import android.annotation.NonNull; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.graphics.drawable.Drawable; 37 import android.metrics.LogMaker; 38 import android.os.Handler; 39 import android.os.Looper; 40 import android.os.Message; 41 import android.text.format.DateUtils; 42 import android.util.ArraySet; 43 import android.util.Log; 44 import android.util.SparseArray; 45 import android.view.View; 46 47 import androidx.annotation.Nullable; 48 import androidx.lifecycle.Lifecycle; 49 import androidx.lifecycle.LifecycleOwner; 50 import androidx.lifecycle.LifecycleRegistry; 51 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.internal.jank.InteractionJankMonitor; 54 import com.android.internal.logging.InstanceId; 55 import com.android.internal.logging.MetricsLogger; 56 import com.android.internal.logging.UiEventLogger; 57 import com.android.settingslib.RestrictedLockUtils; 58 import com.android.settingslib.RestrictedLockUtilsInternal; 59 import com.android.systemui.Dumpable; 60 import com.android.systemui.animation.ActivityLaunchAnimator; 61 import com.android.systemui.plugins.ActivityStarter; 62 import com.android.systemui.plugins.FalsingManager; 63 import com.android.systemui.plugins.qs.DetailAdapter; 64 import com.android.systemui.plugins.qs.QSIconView; 65 import com.android.systemui.plugins.qs.QSTile; 66 import com.android.systemui.plugins.qs.QSTile.State; 67 import com.android.systemui.plugins.statusbar.StatusBarStateController; 68 import com.android.systemui.qs.QSEvent; 69 import com.android.systemui.qs.QSHost; 70 import com.android.systemui.qs.SideLabelTileLayout; 71 import com.android.systemui.qs.logging.QSLogger; 72 73 import java.io.FileDescriptor; 74 import java.io.PrintWriter; 75 import java.util.ArrayList; 76 77 /** 78 * Base quick-settings tile, extend this to create a new tile. 79 * 80 * State management done on a looper provided by the host. Tiles should update state in 81 * handleUpdateState. Callbacks affecting state should use refreshState to trigger another 82 * state update pass on tile looper. 83 * 84 * @param <TState> see above 85 */ 86 public abstract class QSTileImpl<TState extends State> implements QSTile, LifecycleOwner, Dumpable { 87 protected final String TAG = "Tile." + getClass().getSimpleName(); 88 protected static final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG); 89 90 private static final long DEFAULT_STALE_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS; 91 protected static final Object ARG_SHOW_TRANSIENT_ENABLING = new Object(); 92 93 private static final int READY_STATE_NOT_READY = 0; 94 private static final int READY_STATE_READYING = 1; 95 private static final int READY_STATE_READY = 2; 96 97 protected final QSHost mHost; 98 protected final Context mContext; 99 // @NonFinalForTesting 100 protected final H mHandler; 101 protected final Handler mUiHandler; 102 private final ArraySet<Object> mListeners = new ArraySet<>(); 103 private final MetricsLogger mMetricsLogger; 104 private final StatusBarStateController mStatusBarStateController; 105 protected final ActivityStarter mActivityStarter; 106 private final UiEventLogger mUiEventLogger; 107 private final FalsingManager mFalsingManager; 108 private final QSLogger mQSLogger; 109 private volatile int mReadyState; 110 111 private final ArrayList<Callback> mCallbacks = new ArrayList<>(); 112 private final Object mStaleListener = new Object(); 113 protected TState mState; 114 private TState mTmpState; 115 private final InstanceId mInstanceId; 116 private boolean mAnnounceNextStateChange; 117 118 private String mTileSpec; 119 private EnforcedAdmin mEnforcedAdmin; 120 private boolean mShowingDetail; 121 private int mIsFullQs; 122 123 private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); 124 125 /** 126 * Provides a new {@link TState} of the appropriate type to use between this tile and the 127 * corresponding view. 128 * 129 * @return new state to use by the tile. 130 */ newTileState()131 public abstract TState newTileState(); 132 133 /** 134 * Handles clicks by the user. 135 * 136 * Calls to the controller should be made here to set the new state of the device. 137 * 138 * @param view The view that was clicked. 139 */ handleClick(@ullable View view)140 protected abstract void handleClick(@Nullable View view); 141 142 /** 143 * Update state of the tile based on device state 144 * 145 * Called whenever the state of the tile needs to be updated, either after user 146 * interaction or from callbacks from the controller. It populates {@code state} with the 147 * information to display to the user. 148 * 149 * @param state {@link TState} to populate with information to display 150 * @param arg additional arguments needed to populate {@code state} 151 */ handleUpdateState(TState state, Object arg)152 abstract protected void handleUpdateState(TState state, Object arg); 153 154 /** 155 * Declare the category of this tile. 156 * 157 * Categories are defined in {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent} 158 * by editing frameworks/base/proto/src/metrics_constants.proto. 159 */ getMetricsCategory()160 abstract public int getMetricsCategory(); 161 162 /** 163 * Performs initialization of the tile 164 * 165 * Use this to perform initialization of the tile. Empty by default. 166 */ handleInitialize()167 protected void handleInitialize() { 168 169 } 170 QSTileImpl( QSHost host, Looper backgroundLooper, Handler mainHandler, FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger )171 protected QSTileImpl( 172 QSHost host, 173 Looper backgroundLooper, 174 Handler mainHandler, 175 FalsingManager falsingManager, 176 MetricsLogger metricsLogger, 177 StatusBarStateController statusBarStateController, 178 ActivityStarter activityStarter, 179 QSLogger qsLogger 180 ) { 181 mHost = host; 182 mContext = host.getContext(); 183 mInstanceId = host.getNewInstanceId(); 184 mUiEventLogger = host.getUiEventLogger(); 185 186 mUiHandler = mainHandler; 187 mHandler = new H(backgroundLooper); 188 mFalsingManager = falsingManager; 189 mQSLogger = qsLogger; 190 mMetricsLogger = metricsLogger; 191 mStatusBarStateController = statusBarStateController; 192 mActivityStarter = activityStarter; 193 194 resetStates(); 195 mUiHandler.post(() -> mLifecycle.setCurrentState(CREATED)); 196 } 197 resetStates()198 protected final void resetStates() { 199 mState = newTileState(); 200 mTmpState = newTileState(); 201 mState.spec = mTileSpec; 202 mTmpState.spec = mTileSpec; 203 } 204 205 @NonNull 206 @Override getLifecycle()207 public Lifecycle getLifecycle() { 208 return mLifecycle; 209 } 210 211 @Override getInstanceId()212 public InstanceId getInstanceId() { 213 return mInstanceId; 214 } 215 216 /** 217 * Adds or removes a listening client for the tile. If the tile has one or more 218 * listening client it will go into the listening state. 219 */ setListening(Object listener, boolean listening)220 public void setListening(Object listener, boolean listening) { 221 mHandler.obtainMessage(H.SET_LISTENING, listening ? 1 : 0, 0, listener).sendToTarget(); 222 } 223 getStaleTimeout()224 protected long getStaleTimeout() { 225 return DEFAULT_STALE_TIMEOUT; 226 } 227 228 @VisibleForTesting handleStale()229 protected void handleStale() { 230 setListening(mStaleListener, true); 231 } 232 getTileSpec()233 public String getTileSpec() { 234 return mTileSpec; 235 } 236 setTileSpec(String tileSpec)237 public void setTileSpec(String tileSpec) { 238 mTileSpec = tileSpec; 239 mState.spec = tileSpec; 240 mTmpState.spec = tileSpec; 241 } 242 getHost()243 public QSHost getHost() { 244 return mHost; 245 } 246 247 /** 248 * Return the {@link QSIconView} to be used by this tile's view. 249 * 250 * @param context view context for the view 251 * @return icon view for this tile 252 */ createTileView(Context context)253 public QSIconView createTileView(Context context) { 254 return new QSIconViewImpl(context); 255 } 256 getDetailAdapter()257 public DetailAdapter getDetailAdapter() { 258 return null; // optional 259 } 260 createDetailAdapter()261 protected DetailAdapter createDetailAdapter() { 262 throw new UnsupportedOperationException(); 263 } 264 265 /** 266 * Is a startup check whether this device currently supports this tile. 267 * Should not be used to conditionally hide tiles. Only checked on tile 268 * creation or whether should be shown in edit screen. 269 */ isAvailable()270 public boolean isAvailable() { 271 return true; 272 } 273 274 // safe to call from any thread 275 addCallback(Callback callback)276 public void addCallback(Callback callback) { 277 mHandler.obtainMessage(H.ADD_CALLBACK, callback).sendToTarget(); 278 } 279 removeCallback(Callback callback)280 public void removeCallback(Callback callback) { 281 mHandler.obtainMessage(H.REMOVE_CALLBACK, callback).sendToTarget(); 282 } 283 removeCallbacks()284 public void removeCallbacks() { 285 mHandler.sendEmptyMessage(H.REMOVE_CALLBACKS); 286 } 287 click(@ullable View view)288 public void click(@Nullable View view) { 289 mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION) 290 .addTaggedData(FIELD_STATUS_BAR_STATE, 291 mStatusBarStateController.getState()))); 292 mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_CLICK, 0, getMetricsSpec(), 293 getInstanceId()); 294 mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state); 295 if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { 296 mHandler.obtainMessage(H.CLICK, view).sendToTarget(); 297 } 298 } 299 secondaryClick(@ullable View view)300 public void secondaryClick(@Nullable View view) { 301 mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION) 302 .addTaggedData(FIELD_STATUS_BAR_STATE, 303 mStatusBarStateController.getState()))); 304 mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_SECONDARY_CLICK, 0, getMetricsSpec(), 305 getInstanceId()); 306 mQSLogger.logTileSecondaryClick(mTileSpec, mStatusBarStateController.getState(), 307 mState.state); 308 mHandler.obtainMessage(H.SECONDARY_CLICK, view).sendToTarget(); 309 } 310 311 @Override longClick(@ullable View view)312 public void longClick(@Nullable View view) { 313 mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION) 314 .addTaggedData(FIELD_STATUS_BAR_STATE, 315 mStatusBarStateController.getState()))); 316 mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_LONG_PRESS, 0, getMetricsSpec(), 317 getInstanceId()); 318 mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state); 319 mHandler.obtainMessage(H.LONG_CLICK, view).sendToTarget(); 320 } 321 populate(LogMaker logMaker)322 public LogMaker populate(LogMaker logMaker) { 323 if (mState instanceof BooleanState) { 324 logMaker.addTaggedData(FIELD_QS_VALUE, ((BooleanState) mState).value ? 1 : 0); 325 } 326 return logMaker.setSubtype(getMetricsCategory()) 327 .addTaggedData(FIELD_IS_FULL_QS, mIsFullQs) 328 .addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec)); 329 } 330 showDetail(boolean show)331 public void showDetail(boolean show) { 332 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget(); 333 } 334 refreshState()335 public void refreshState() { 336 refreshState(null); 337 } 338 refreshState(Object arg)339 protected final void refreshState(Object arg) { 340 mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget(); 341 } 342 userSwitch(int newUserId)343 public void userSwitch(int newUserId) { 344 mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget(); 345 } 346 fireToggleStateChanged(boolean state)347 public void fireToggleStateChanged(boolean state) { 348 mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget(); 349 } 350 fireScanStateChanged(boolean state)351 public void fireScanStateChanged(boolean state) { 352 mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget(); 353 } 354 destroy()355 public void destroy() { 356 mHandler.sendEmptyMessage(H.DESTROY); 357 } 358 359 /** 360 * Schedules initialization of the tile. 361 * 362 * Should be called upon creation of the tile, before performing other operations 363 */ initialize()364 public void initialize() { 365 mHandler.sendEmptyMessage(H.INITIALIZE); 366 } 367 getState()368 public TState getState() { 369 return mState; 370 } 371 setDetailListening(boolean listening)372 public void setDetailListening(boolean listening) { 373 // optional 374 } 375 376 // call only on tile worker looper 377 handleAddCallback(Callback callback)378 private void handleAddCallback(Callback callback) { 379 mCallbacks.add(callback); 380 callback.onStateChanged(mState); 381 } 382 handleRemoveCallback(Callback callback)383 private void handleRemoveCallback(Callback callback) { 384 mCallbacks.remove(callback); 385 } 386 handleRemoveCallbacks()387 private void handleRemoveCallbacks() { 388 mCallbacks.clear(); 389 } 390 391 /** 392 * Posts a stale message to the background thread. 393 */ postStale()394 public void postStale() { 395 mHandler.sendEmptyMessage(H.STALE); 396 } 397 398 /** 399 * Handles secondary click on the tile. 400 * 401 * Defaults to {@link QSTileImpl#handleClick} 402 * 403 * @param view The view that was clicked. 404 */ handleSecondaryClick(@ullable View view)405 protected void handleSecondaryClick(@Nullable View view) { 406 // Default to normal click. 407 handleClick(view); 408 } 409 410 /** 411 * Handles long click on the tile by launching the {@link Intent} defined in 412 * {@link QSTileImpl#getLongClickIntent}. 413 * 414 * @param view The view from which the opening window will be animated. 415 */ handleLongClick(@ullable View view)416 protected void handleLongClick(@Nullable View view) { 417 ActivityLaunchAnimator.Controller animationController = 418 view != null ? ActivityLaunchAnimator.Controller.fromView(view, 419 InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) : null; 420 mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0, 421 animationController); 422 } 423 424 /** 425 * Returns an intent to be launched when the tile is long pressed. 426 * 427 * @return the intent to launch 428 */ getLongClickIntent()429 public abstract Intent getLongClickIntent(); 430 handleRefreshState(Object arg)431 protected void handleRefreshState(Object arg) { 432 handleUpdateState(mTmpState, arg); 433 boolean changed = mTmpState.copyTo(mState); 434 if (mReadyState == READY_STATE_READYING) { 435 mReadyState = READY_STATE_READY; 436 changed = true; 437 } 438 if (changed) { 439 mQSLogger.logTileUpdated(mTileSpec, mState); 440 handleStateChanged(); 441 } 442 mHandler.removeMessages(H.STALE); 443 mHandler.sendEmptyMessageDelayed(H.STALE, getStaleTimeout()); 444 setListening(mStaleListener, false); 445 } 446 handleStateChanged()447 private void handleStateChanged() { 448 boolean delayAnnouncement = shouldAnnouncementBeDelayed(); 449 if (mCallbacks.size() != 0) { 450 for (int i = 0; i < mCallbacks.size(); i++) { 451 mCallbacks.get(i).onStateChanged(mState); 452 } 453 if (mAnnounceNextStateChange && !delayAnnouncement) { 454 String announcement = composeChangeAnnouncement(); 455 if (announcement != null) { 456 mCallbacks.get(0).onAnnouncementRequested(announcement); 457 } 458 } 459 } 460 mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement; 461 } 462 shouldAnnouncementBeDelayed()463 protected boolean shouldAnnouncementBeDelayed() { 464 return false; 465 } 466 composeChangeAnnouncement()467 protected String composeChangeAnnouncement() { 468 return null; 469 } 470 handleShowDetail(boolean show)471 private void handleShowDetail(boolean show) { 472 mShowingDetail = show; 473 for (int i = 0; i < mCallbacks.size(); i++) { 474 mCallbacks.get(i).onShowDetail(show); 475 } 476 } 477 isShowingDetail()478 protected boolean isShowingDetail() { 479 return mShowingDetail; 480 } 481 handleToggleStateChanged(boolean state)482 private void handleToggleStateChanged(boolean state) { 483 for (int i = 0; i < mCallbacks.size(); i++) { 484 mCallbacks.get(i).onToggleStateChanged(state); 485 } 486 } 487 handleScanStateChanged(boolean state)488 private void handleScanStateChanged(boolean state) { 489 for (int i = 0; i < mCallbacks.size(); i++) { 490 mCallbacks.get(i).onScanStateChanged(state); 491 } 492 } 493 handleUserSwitch(int newUserId)494 protected void handleUserSwitch(int newUserId) { 495 handleRefreshState(null); 496 } 497 handleSetListeningInternal(Object listener, boolean listening)498 private void handleSetListeningInternal(Object listener, boolean listening) { 499 // This should be used to go from resumed to paused. Listening for ON_RESUME and ON_PAUSE 500 // in this lifecycle will determine the listening window. 501 if (listening) { 502 if (mListeners.add(listener) && mListeners.size() == 1) { 503 if (DEBUG) Log.d(TAG, "handleSetListening true"); 504 handleSetListening(listening); 505 mUiHandler.post(() -> { 506 // This tile has been destroyed, the state should not change anymore and we 507 // should not refresh it anymore. 508 if (mLifecycle.getCurrentState().equals(DESTROYED)) return; 509 mLifecycle.setCurrentState(RESUMED); 510 if (mReadyState == READY_STATE_NOT_READY) { 511 mReadyState = READY_STATE_READYING; 512 } 513 refreshState(); // Ensure we get at least one refresh after listening. 514 }); 515 } 516 } else { 517 if (mListeners.remove(listener) && mListeners.size() == 0) { 518 if (DEBUG) Log.d(TAG, "handleSetListening false"); 519 handleSetListening(listening); 520 mUiHandler.post(() -> { 521 // This tile has been destroyed, the state should not change anymore. 522 if (mLifecycle.getCurrentState().equals(DESTROYED)) return; 523 mLifecycle.setCurrentState(STARTED); 524 }); 525 } 526 } 527 updateIsFullQs(); 528 } 529 updateIsFullQs()530 private void updateIsFullQs() { 531 for (Object listener : mListeners) { 532 if (SideLabelTileLayout.class.equals(listener.getClass())) { 533 mIsFullQs = 1; 534 return; 535 } 536 } 537 mIsFullQs = 0; 538 } 539 540 @CallSuper handleSetListening(boolean listening)541 protected void handleSetListening(boolean listening) { 542 if (mTileSpec != null) { 543 mQSLogger.logTileChangeListening(mTileSpec, listening); 544 } 545 } 546 handleDestroy()547 protected void handleDestroy() { 548 mQSLogger.logTileDestroyed(mTileSpec, "Handle destroy"); 549 if (mListeners.size() != 0) { 550 handleSetListening(false); 551 mListeners.clear(); 552 } 553 mCallbacks.clear(); 554 mHandler.removeCallbacksAndMessages(null); 555 // This will force it to be removed from all controllers that may have it registered. 556 mUiHandler.post(() -> { 557 mLifecycle.setCurrentState(DESTROYED); 558 }); 559 } 560 checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction)561 protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) { 562 EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, 563 userRestriction, mHost.getUserId()); 564 if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext, 565 userRestriction, mHost.getUserId())) { 566 state.disabledByPolicy = true; 567 mEnforcedAdmin = admin; 568 } else { 569 state.disabledByPolicy = false; 570 mEnforcedAdmin = null; 571 } 572 } 573 574 @Override getMetricsSpec()575 public String getMetricsSpec() { 576 return mTileSpec; 577 } 578 579 /** 580 * Provides a default label for the tile. 581 * @return default label for the tile. 582 */ getTileLabel()583 public abstract CharSequence getTileLabel(); 584 585 /** 586 * @return {@code true} if the tile has refreshed state at least once after having set its 587 * lifecycle to {@link Lifecycle.State#RESUMED}. 588 */ 589 @Override isTileReady()590 public boolean isTileReady() { 591 return mReadyState == READY_STATE_READY; 592 } 593 594 protected final class H extends Handler { 595 private static final int ADD_CALLBACK = 1; 596 private static final int CLICK = 2; 597 private static final int SECONDARY_CLICK = 3; 598 private static final int LONG_CLICK = 4; 599 private static final int REFRESH_STATE = 5; 600 private static final int SHOW_DETAIL = 6; 601 private static final int USER_SWITCH = 7; 602 private static final int TOGGLE_STATE_CHANGED = 8; 603 private static final int SCAN_STATE_CHANGED = 9; 604 private static final int DESTROY = 10; 605 private static final int REMOVE_CALLBACKS = 11; 606 private static final int REMOVE_CALLBACK = 12; 607 private static final int SET_LISTENING = 13; 608 @VisibleForTesting 609 protected static final int STALE = 14; 610 private static final int INITIALIZE = 15; 611 612 @VisibleForTesting H(Looper looper)613 protected H(Looper looper) { 614 super(looper); 615 } 616 617 @Override handleMessage(Message msg)618 public void handleMessage(Message msg) { 619 String name = null; 620 try { 621 if (msg.what == ADD_CALLBACK) { 622 name = "handleAddCallback"; 623 handleAddCallback((QSTile.Callback) msg.obj); 624 } else if (msg.what == REMOVE_CALLBACKS) { 625 name = "handleRemoveCallbacks"; 626 handleRemoveCallbacks(); 627 } else if (msg.what == REMOVE_CALLBACK) { 628 name = "handleRemoveCallback"; 629 handleRemoveCallback((QSTile.Callback) msg.obj); 630 } else if (msg.what == CLICK) { 631 name = "handleClick"; 632 if (mState.disabledByPolicy) { 633 Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent( 634 mContext, mEnforcedAdmin); 635 mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); 636 } else { 637 handleClick((View) msg.obj); 638 } 639 } else if (msg.what == SECONDARY_CLICK) { 640 name = "handleSecondaryClick"; 641 handleSecondaryClick((View) msg.obj); 642 } else if (msg.what == LONG_CLICK) { 643 name = "handleLongClick"; 644 handleLongClick((View) msg.obj); 645 } else if (msg.what == REFRESH_STATE) { 646 name = "handleRefreshState"; 647 handleRefreshState(msg.obj); 648 } else if (msg.what == SHOW_DETAIL) { 649 name = "handleShowDetail"; 650 handleShowDetail(msg.arg1 != 0); 651 } else if (msg.what == USER_SWITCH) { 652 name = "handleUserSwitch"; 653 handleUserSwitch(msg.arg1); 654 } else if (msg.what == TOGGLE_STATE_CHANGED) { 655 name = "handleToggleStateChanged"; 656 handleToggleStateChanged(msg.arg1 != 0); 657 } else if (msg.what == SCAN_STATE_CHANGED) { 658 name = "handleScanStateChanged"; 659 handleScanStateChanged(msg.arg1 != 0); 660 } else if (msg.what == DESTROY) { 661 name = "handleDestroy"; 662 handleDestroy(); 663 } else if (msg.what == SET_LISTENING) { 664 name = "handleSetListeningInternal"; 665 handleSetListeningInternal(msg.obj, msg.arg1 != 0); 666 } else if (msg.what == STALE) { 667 name = "handleStale"; 668 handleStale(); 669 } else if (msg.what == INITIALIZE) { 670 name = "initialize"; 671 handleInitialize(); 672 } else { 673 throw new IllegalArgumentException("Unknown msg: " + msg.what); 674 } 675 } catch (Throwable t) { 676 final String error = "Error in " + name; 677 Log.w(TAG, error, t); 678 mHost.warn(error, t); 679 } 680 } 681 } 682 683 public static class DrawableIcon extends Icon { 684 protected final Drawable mDrawable; 685 protected final Drawable mInvisibleDrawable; 686 DrawableIcon(Drawable drawable)687 public DrawableIcon(Drawable drawable) { 688 mDrawable = drawable; 689 mInvisibleDrawable = drawable.getConstantState().newDrawable(); 690 } 691 692 @Override getDrawable(Context context)693 public Drawable getDrawable(Context context) { 694 return mDrawable; 695 } 696 697 @Override getInvisibleDrawable(Context context)698 public Drawable getInvisibleDrawable(Context context) { 699 return mInvisibleDrawable; 700 } 701 702 @Override 703 @NonNull toString()704 public String toString() { 705 return "DrawableIcon"; 706 } 707 } 708 709 public static class DrawableIconWithRes extends DrawableIcon { 710 private final int mId; 711 DrawableIconWithRes(Drawable drawable, int id)712 public DrawableIconWithRes(Drawable drawable, int id) { 713 super(drawable); 714 mId = id; 715 } 716 717 @Override equals(Object o)718 public boolean equals(Object o) { 719 return o instanceof DrawableIconWithRes && ((DrawableIconWithRes) o).mId == mId; 720 } 721 722 @Override 723 @NonNull toString()724 public String toString() { 725 return String.format("DrawableIconWithRes[resId=0x%08x]", mId); 726 } 727 } 728 729 public static class ResourceIcon extends Icon { 730 private static final SparseArray<Icon> ICONS = new SparseArray<Icon>(); 731 732 protected final int mResId; 733 ResourceIcon(int resId)734 private ResourceIcon(int resId) { 735 mResId = resId; 736 } 737 get(int resId)738 public static synchronized Icon get(int resId) { 739 Icon icon = ICONS.get(resId); 740 if (icon == null) { 741 icon = new ResourceIcon(resId); 742 ICONS.put(resId, icon); 743 } 744 return icon; 745 } 746 747 @Override getDrawable(Context context)748 public Drawable getDrawable(Context context) { 749 return context.getDrawable(mResId); 750 } 751 752 @Override getInvisibleDrawable(Context context)753 public Drawable getInvisibleDrawable(Context context) { 754 return context.getDrawable(mResId); 755 } 756 757 @Override equals(Object o)758 public boolean equals(Object o) { 759 return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId; 760 } 761 762 @Override 763 @NonNull toString()764 public String toString() { 765 return String.format("ResourceIcon[resId=0x%08x]", mResId); 766 } 767 } 768 769 protected static class AnimationIcon extends ResourceIcon { 770 private final int mAnimatedResId; 771 AnimationIcon(int resId, int staticResId)772 public AnimationIcon(int resId, int staticResId) { 773 super(staticResId); 774 mAnimatedResId = resId; 775 } 776 777 @Override getDrawable(Context context)778 public Drawable getDrawable(Context context) { 779 // workaround: get a clean state for every new AVD 780 return context.getDrawable(mAnimatedResId).getConstantState().newDrawable(); 781 } 782 783 @Override 784 @NonNull toString()785 public String toString() { 786 return String.format("AnimationIcon[resId=0x%08x]", mResId); 787 } 788 } 789 790 /** 791 * Dumps the state of this tile along with its name. 792 * 793 * This may be used for CTS testing of tiles. 794 */ 795 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)796 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 797 pw.println(this.getClass().getSimpleName() + ":"); 798 pw.print(" "); pw.println(getState().toString()); 799 } 800 } 801