blob: 795d0627c447488a942888a174e2bac3ebe34e73 [file] [log] [blame]
John Spurlockaf8d6c42014-05-07 17:49:08 -04001/*
Jason Monk702e2eb2017-03-03 16:53:44 -05002 * Copyright (C) 2017 The Android Open Source Project
John Spurlockaf8d6c42014-05-07 17:49:08 -04003 *
Jason Monk702e2eb2017-03-03 16:53:44 -05004 * 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
John Spurlockaf8d6c42014-05-07 17:49:08 -04006 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
Jason Monk702e2eb2017-03-03 16:53:44 -05009 * 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.
John Spurlockaf8d6c42014-05-07 17:49:08 -040013 */
14
Jason Monk702e2eb2017-03-03 16:53:44 -050015package com.android.systemui.qs.tileimpl;
16
Jason Monkfa452ef2018-12-26 17:26:10 -050017import static androidx.lifecycle.Lifecycle.State.RESUMED;
Fabian Kozynskif4559512020-06-23 14:02:58 -040018import static androidx.lifecycle.Lifecycle.State.STARTED;
Jason Monkfa452ef2018-12-26 17:26:10 -050019
Jason Monk8c09ac72017-03-16 11:53:40 -040020import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_CLICK;
21import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_LONG_PRESS;
22import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_SECONDARY_CLICK;
Susi Kharraz-Post9b033672018-11-28 08:14:07 -050023import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_IS_FULL_QS;
Jason Monk8c09ac72017-03-16 11:53:40 -040024import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_POSITION;
25import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_VALUE;
Susi Kharraz-Post9b033672018-11-28 08:14:07 -050026import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_STATUS_BAR_STATE;
Jason Monk8c09ac72017-03-16 11:53:40 -040027import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_ACTION;
Jason Monk702e2eb2017-03-03 16:53:44 -050028import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
John Spurlockaf8d6c42014-05-07 17:49:08 -040029
Fabian Kozynskica30f572019-10-24 10:01:04 -040030import android.annotation.CallSuper;
31import android.annotation.NonNull;
Sudheer Shankab6fc9312016-01-27 19:59:03 +000032import android.app.ActivityManager;
John Spurlockaf8d6c42014-05-07 17:49:08 -040033import android.content.Context;
34import android.content.Intent;
John Spurlock444eb2e2014-05-14 13:32:14 -040035import android.graphics.drawable.Drawable;
Jason Monk8c09ac72017-03-16 11:53:40 -040036import android.metrics.LogMaker;
John Spurlockaf8d6c42014-05-07 17:49:08 -040037import android.os.Handler;
38import android.os.Looper;
39import android.os.Message;
Jason Monk32508852017-01-18 09:17:13 -050040import android.service.quicksettings.Tile;
Jason Monk1c6116c2017-09-06 17:33:01 -040041import android.text.format.DateUtils;
Jason Monk1bec6af2016-05-31 15:40:58 -040042import android.util.ArraySet;
John Spurlockaf8d6c42014-05-07 17:49:08 -040043import android.util.Log;
John Spurlock2d695812014-10-30 13:25:21 -040044import android.util.SparseArray;
Sudheer Shanka1c7cda82015-12-31 14:46:02 +000045
Jason Monkfa452ef2018-12-26 17:26:10 -050046import androidx.lifecycle.Lifecycle;
47import androidx.lifecycle.LifecycleOwner;
48import androidx.lifecycle.LifecycleRegistry;
49
Jason Monk1c6116c2017-09-06 17:33:01 -040050import com.android.internal.annotations.VisibleForTesting;
Fabian Kozynski2ff6df92020-04-24 12:00:49 -040051import com.android.internal.logging.InstanceId;
Jason Monk96defbe2016-03-29 16:51:03 -040052import com.android.internal.logging.MetricsLogger;
Fabian Kozynski2ff6df92020-04-24 12:00:49 -040053import com.android.internal.logging.UiEventLogger;
Sudheer Shanka1c7cda82015-12-31 14:46:02 +000054import com.android.settingslib.RestrictedLockUtils;
Philip P. Moltmann4e615e62018-08-28 14:57:49 -070055import com.android.settingslib.RestrictedLockUtilsInternal;
Jason Monk702e2eb2017-03-03 16:53:44 -050056import com.android.settingslib.Utils;
Jason Monk9c7844c2017-01-18 15:21:53 -050057import com.android.systemui.Dependency;
Fabian Kozynski00d494d2019-04-04 09:53:50 -040058import com.android.systemui.Dumpable;
Rohan Shahdb2cfa32018-02-20 11:27:22 -080059import com.android.systemui.Prefs;
Jason Monkec34da82017-02-24 15:57:05 -050060import com.android.systemui.plugins.ActivityStarter;
Jason Monke5b770e2017-03-03 21:49:29 -050061import com.android.systemui.plugins.qs.DetailAdapter;
Jason Monk702e2eb2017-03-03 16:53:44 -050062import com.android.systemui.plugins.qs.QSIconView;
63import com.android.systemui.plugins.qs.QSTile;
64import com.android.systemui.plugins.qs.QSTile.State;
Beverly8fdb5332019-02-04 14:29:49 -050065import com.android.systemui.plugins.statusbar.StatusBarStateController;
Jason Monkf8c2f7b2017-09-06 09:22:29 -040066import com.android.systemui.qs.PagedTileLayout.TilePage;
Fabian Kozynski2ff6df92020-04-24 12:00:49 -040067import com.android.systemui.qs.QSEvent;
Jason Monk702e2eb2017-03-03 16:53:44 -050068import com.android.systemui.qs.QSHost;
Rohan Shahd3cf7562018-02-23 11:12:28 -080069import com.android.systemui.qs.QuickStatusBarHeader;
Fabian Kozynskica30f572019-10-24 10:01:04 -040070import com.android.systemui.qs.logging.QSLogger;
John Spurlockaf8d6c42014-05-07 17:49:08 -040071
Fabian Kozynski00d494d2019-04-04 09:53:50 -040072import java.io.FileDescriptor;
73import java.io.PrintWriter;
Jason Monkca894a02016-01-12 15:30:22 -050074import java.util.ArrayList;
Sudheer Shanka1c7cda82015-12-31 14:46:02 +000075
John Spurlockaf8d6c42014-05-07 17:49:08 -040076/**
77 * Base quick-settings tile, extend this to create a new tile.
78 *
79 * State management done on a looper provided by the host. Tiles should update state in
80 * handleUpdateState. Callbacks affecting state should use refreshState to trigger another
81 * state update pass on tile looper.
Susi Kharraz-Post9b033672018-11-28 08:14:07 -050082 *
83 * @param <TState> see above
John Spurlockaf8d6c42014-05-07 17:49:08 -040084 */
Fabian Kozynski00d494d2019-04-04 09:53:50 -040085public abstract class QSTileImpl<TState extends State> implements QSTile, LifecycleOwner, Dumpable {
Jason Monkbbadff82015-11-06 15:47:26 -050086 protected final String TAG = "Tile." + getClass().getSimpleName();
87 protected static final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG);
John Spurlockaf8d6c42014-05-07 17:49:08 -040088
Jason Monk1c6116c2017-09-06 17:33:01 -040089 private static final long DEFAULT_STALE_TIMEOUT = 10 * DateUtils.MINUTE_IN_MILLIS;
Amin Shaikhd03a7432018-03-01 15:46:55 -050090 protected static final Object ARG_SHOW_TRANSIENT_ENABLING = new Object();
Jason Monk1c6116c2017-09-06 17:33:01 -040091
Jason Monk702e2eb2017-03-03 16:53:44 -050092 protected final QSHost mHost;
John Spurlockaf8d6c42014-05-07 17:49:08 -040093 protected final Context mContext;
Jason Monk1c6116c2017-09-06 17:33:01 -040094 // @NonFinalForTesting
95 protected H mHandler = new H(Dependency.get(Dependency.BG_LOOPER));
John Spurlock76c43b92014-05-13 21:10:51 -040096 protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
Jason Monk1bec6af2016-05-31 15:40:58 -040097 private final ArraySet<Object> mListeners = new ArraySet<>();
Jason Monk8c09ac72017-03-16 11:53:40 -040098 private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
Susi Kharraz-Post9b033672018-11-28 08:14:07 -050099 private final StatusBarStateController
100 mStatusBarStateController = Dependency.get(StatusBarStateController.class);
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400101 private final UiEventLogger mUiEventLogger;
Fabian Kozynskica30f572019-10-24 10:01:04 -0400102 private final QSLogger mQSLogger;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400103
Jason Monkca894a02016-01-12 15:30:22 -0500104 private final ArrayList<Callback> mCallbacks = new ArrayList<>();
Jason Monk1c6116c2017-09-06 17:33:01 -0400105 private final Object mStaleListener = new Object();
Fabian Kozynski67ffcf82019-09-05 16:32:12 -0400106 protected TState mState;
107 private TState mTmpState;
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400108 private final InstanceId mInstanceId;
Selim Cinek4fda7b22014-08-18 22:07:25 +0200109 private boolean mAnnounceNextStateChange;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400110
Jason Monkbd6dbb02015-09-03 15:46:25 -0400111 private String mTileSpec;
Jason Monk702e2eb2017-03-03 16:53:44 -0500112 private EnforcedAdmin mEnforcedAdmin;
Jason Monkbe3235a2017-04-05 09:29:53 -0400113 private boolean mShowingDetail;
Amin Shaikh572230b2018-03-20 17:24:57 -0400114 private int mIsFullQs;
Jason Monkbd6dbb02015-09-03 15:46:25 -0400115
Jason Monkfa452ef2018-12-26 17:26:10 -0500116 private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
117
Fabian Kozynski01f07682019-06-06 14:07:17 -0400118 /**
119 * Provides a new {@link TState} of the appropriate type to use between this tile and the
120 * corresponding view.
121 *
122 * @return new state to use by the tile.
123 */
Jason Monk62b63a02016-02-02 15:15:31 -0500124 public abstract TState newTileState();
Jason Monk32508852017-01-18 09:17:13 -0500125
Fabian Kozynski01f07682019-06-06 14:07:17 -0400126 /**
127 * Handles clicks by the user.
128 *
129 * Calls to the controller should be made here to set the new state of the device.
130 */
Chris Wren9e7283f2015-05-08 17:23:47 -0400131 abstract protected void handleClick();
Jason Monk32508852017-01-18 09:17:13 -0500132
Fabian Kozynski01f07682019-06-06 14:07:17 -0400133 /**
134 * Update state of the tile based on device state
135 *
136 * Called whenever the state of the tile needs to be updated, either after user
137 * interaction or from callbacks from the controller. It populates {@code state} with the
138 * information to display to the user.
139 *
140 * @param state {@link TState} to populate with information to display
141 * @param arg additional arguments needed to populate {@code state}
142 */
John Spurlockaf8d6c42014-05-07 17:49:08 -0400143 abstract protected void handleUpdateState(TState state, Object arg);
144
Chris Wren457a21c2015-05-06 17:50:34 -0400145 /**
146 * Declare the category of this tile.
147 *
Jason Monk8c09ac72017-03-16 11:53:40 -0400148 * Categories are defined in {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}
Chris Wrenf6e9228b2016-01-26 18:04:35 -0500149 * by editing frameworks/base/proto/src/metrics_constants.proto.
Chris Wren457a21c2015-05-06 17:50:34 -0400150 */
151 abstract public int getMetricsCategory();
152
Jason Monk702e2eb2017-03-03 16:53:44 -0500153 protected QSTileImpl(QSHost host) {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400154 mHost = host;
155 mContext = host.getContext();
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400156 mInstanceId = host.getNewInstanceId();
Fabian Kozynski67ffcf82019-09-05 16:32:12 -0400157 mState = newTileState();
158 mTmpState = newTileState();
Fabian Kozynskica30f572019-10-24 10:01:04 -0400159 mQSLogger = host.getQSLogger();
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400160 mUiEventLogger = host.getUiEventLogger();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400161 }
Jason Monkbd6dbb02015-09-03 15:46:25 -0400162
Fabian Kozynski05843f02019-06-28 13:19:57 -0400163 protected final void resetStates() {
164 mState = newTileState();
165 mTmpState = newTileState();
166 }
167
Jason Monkfa452ef2018-12-26 17:26:10 -0500168 @NonNull
169 @Override
170 public Lifecycle getLifecycle() {
171 return mLifecycle;
172 }
173
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400174 @Override
175 public InstanceId getInstanceId() {
176 return mInstanceId;
177 }
178
Jason Monk1bec6af2016-05-31 15:40:58 -0400179 /**
180 * Adds or removes a listening client for the tile. If the tile has one or more
181 * listening client it will go into the listening state.
182 */
183 public void setListening(Object listener, boolean listening) {
Amin Shaikh572230b2018-03-20 17:24:57 -0400184 mHandler.obtainMessage(H.SET_LISTENING, listening ? 1 : 0, 0, listener).sendToTarget();
Jason Monk1bec6af2016-05-31 15:40:58 -0400185 }
186
Jason Monk1c6116c2017-09-06 17:33:01 -0400187 protected long getStaleTimeout() {
188 return DEFAULT_STALE_TIMEOUT;
189 }
190
191 @VisibleForTesting
192 protected void handleStale() {
193 setListening(mStaleListener, true);
194 }
195
Jason Monkbd6dbb02015-09-03 15:46:25 -0400196 public String getTileSpec() {
197 return mTileSpec;
198 }
199
200 public void setTileSpec(String tileSpec) {
201 mTileSpec = tileSpec;
202 }
203
Jason Monk702e2eb2017-03-03 16:53:44 -0500204 public QSHost getHost() {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400205 return mHost;
206 }
207
Fabian Kozynski01f07682019-06-06 14:07:17 -0400208 /**
209 * Return the {@link QSIconView} to be used by this tile's view.
210 *
211 * @param context view context for the view
212 * @return icon view for this tile
213 */
Jason Monkdc35dcb2015-12-04 16:36:15 -0500214 public QSIconView createTileView(Context context) {
Jason Monk702e2eb2017-03-03 16:53:44 -0500215 return new QSIconViewImpl(context);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400216 }
217
John Spurlock7f8f22a2014-07-02 18:54:17 -0400218 public DetailAdapter getDetailAdapter() {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400219 return null; // optional
220 }
221
Jason Monk32508852017-01-18 09:17:13 -0500222 protected DetailAdapter createDetailAdapter() {
223 throw new UnsupportedOperationException();
224 }
Muyuan Li0e9f5382016-04-27 15:51:15 -0700225
Jason Monkc3f42c12016-02-05 12:33:13 -0500226 /**
227 * Is a startup check whether this device currently supports this tile.
228 * Should not be used to conditionally hide tiles. Only checked on tile
229 * creation or whether should be shown in edit screen.
230 */
231 public boolean isAvailable() {
232 return true;
233 }
234
John Spurlockaf8d6c42014-05-07 17:49:08 -0400235 // safe to call from any thread
236
Jason Monkca894a02016-01-12 15:30:22 -0500237 public void addCallback(Callback callback) {
238 mHandler.obtainMessage(H.ADD_CALLBACK, callback).sendToTarget();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400239 }
240
Xiaohui Chen08e266c2016-04-18 12:53:28 -0700241 public void removeCallback(Callback callback) {
242 mHandler.obtainMessage(H.REMOVE_CALLBACK, callback).sendToTarget();
243 }
244
Jason Monk9d02a432016-01-20 16:33:46 -0500245 public void removeCallbacks() {
246 mHandler.sendEmptyMessage(H.REMOVE_CALLBACKS);
247 }
248
John Spurlockaf8d6c42014-05-07 17:49:08 -0400249 public void click() {
Susi Kharraz-Post9b033672018-11-28 08:14:07 -0500250 mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION)
251 .addTaggedData(FIELD_STATUS_BAR_STATE,
252 mStatusBarStateController.getState())));
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400253 mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_CLICK, 0, getMetricsSpec(),
254 getInstanceId());
Fabian Kozynskica30f572019-10-24 10:01:04 -0400255 mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400256 mHandler.sendEmptyMessage(H.CLICK);
257 }
258
259 public void secondaryClick() {
Susi Kharraz-Post9b033672018-11-28 08:14:07 -0500260 mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION)
261 .addTaggedData(FIELD_STATUS_BAR_STATE,
262 mStatusBarStateController.getState())));
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400263 mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_SECONDARY_CLICK, 0, getMetricsSpec(),
264 getInstanceId());
Fabian Kozynskica30f572019-10-24 10:01:04 -0400265 mQSLogger.logTileSecondaryClick(mTileSpec, mStatusBarStateController.getState(),
266 mState.state);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400267 mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
268 }
269
John Spurlockc247b8f2014-11-06 23:06:25 -0500270 public void longClick() {
Susi Kharraz-Post9b033672018-11-28 08:14:07 -0500271 mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)
272 .addTaggedData(FIELD_STATUS_BAR_STATE,
273 mStatusBarStateController.getState())));
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400274 mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_LONG_PRESS, 0, getMetricsSpec(),
275 getInstanceId());
Fabian Kozynskica30f572019-10-24 10:01:04 -0400276 mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state);
John Spurlockc247b8f2014-11-06 23:06:25 -0500277 mHandler.sendEmptyMessage(H.LONG_CLICK);
Rohan Shahdb2cfa32018-02-20 11:27:22 -0800278
279 Prefs.putInt(
280 mContext,
281 Prefs.Key.QS_LONG_PRESS_TOOLTIP_SHOWN_COUNT,
Rohan Shahd3cf7562018-02-23 11:12:28 -0800282 QuickStatusBarHeader.MAX_TOOLTIP_SHOWN_COUNT);
John Spurlockc247b8f2014-11-06 23:06:25 -0500283 }
284
Jason Monkcb4b31d2017-05-03 10:37:34 -0400285 public LogMaker populate(LogMaker logMaker) {
Jason Monk8c09ac72017-03-16 11:53:40 -0400286 if (mState instanceof BooleanState) {
287 logMaker.addTaggedData(FIELD_QS_VALUE, ((BooleanState) mState).value ? 1 : 0);
288 }
289 return logMaker.setSubtype(getMetricsCategory())
Susi Kharraz-Post9b033672018-11-28 08:14:07 -0500290 .addTaggedData(FIELD_IS_FULL_QS, mIsFullQs)
Jason Monk8c09ac72017-03-16 11:53:40 -0400291 .addTaggedData(FIELD_QS_POSITION, mHost.indexOf(mTileSpec));
292 }
293
John Spurlockaf8d6c42014-05-07 17:49:08 -0400294 public void showDetail(boolean show) {
295 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
296 }
297
Evan Laird6b284732017-02-28 17:27:04 -0500298 public void refreshState() {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400299 refreshState(null);
300 }
301
302 protected final void refreshState(Object arg) {
303 mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
304 }
305
306 public void userSwitch(int newUserId) {
Adrian Roos32d88e82014-09-24 17:08:22 +0200307 mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400308 }
309
John Spurlock7f8f22a2014-07-02 18:54:17 -0400310 public void fireToggleStateChanged(boolean state) {
311 mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
312 }
313
John Spurlock486b78e2014-07-07 08:37:56 -0400314 public void fireScanStateChanged(boolean state) {
315 mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
316 }
317
John Spurlockbceed062014-08-10 18:04:16 -0400318 public void destroy() {
319 mHandler.sendEmptyMessage(H.DESTROY);
320 }
321
322 public TState getState() {
323 return mState;
324 }
325
Jason Monk0d6a1c42015-04-20 16:38:51 -0400326 public void setDetailListening(boolean listening) {
327 // optional
328 }
329
John Spurlockaf8d6c42014-05-07 17:49:08 -0400330 // call only on tile worker looper
331
Jason Monkca894a02016-01-12 15:30:22 -0500332 private void handleAddCallback(Callback callback) {
333 mCallbacks.add(callback);
Jason Monk968d2692016-06-17 13:48:44 -0400334 callback.onStateChanged(mState);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400335 }
336
Xiaohui Chen08e266c2016-04-18 12:53:28 -0700337 private void handleRemoveCallback(Callback callback) {
338 mCallbacks.remove(callback);
339 }
340
Jason Monk9d02a432016-01-20 16:33:46 -0500341 private void handleRemoveCallbacks() {
342 mCallbacks.clear();
343 }
344
Fabian Kozynski01f07682019-06-06 14:07:17 -0400345 /**
346 * Handles secondary click on the tile.
347 *
348 * Defaults to {@link QSTileImpl#handleClick}
349 */
John Spurlockaf8d6c42014-05-07 17:49:08 -0400350 protected void handleSecondaryClick() {
Jason Monkdc35dcb2015-12-04 16:36:15 -0500351 // Default to normal click.
352 handleClick();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400353 }
354
Fabian Kozynski01f07682019-06-06 14:07:17 -0400355 /**
356 * Handles long click on the tile by launching the {@link Intent} defined in
357 * {@link QSTileImpl#getLongClickIntent}
358 */
John Spurlockc247b8f2014-11-06 23:06:25 -0500359 protected void handleLongClick() {
Jason Monk9c7844c2017-01-18 15:21:53 -0500360 Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
361 getLongClickIntent(), 0);
John Spurlockc247b8f2014-11-06 23:06:25 -0500362 }
363
Fabian Kozynski01f07682019-06-06 14:07:17 -0400364 /**
365 * Returns an intent to be launched when the tile is long pressed.
366 *
367 * @return the intent to launch
368 */
Jason Monk76c67aa2016-02-19 14:49:42 -0500369 public abstract Intent getLongClickIntent();
370
John Spurlockaf8d6c42014-05-07 17:49:08 -0400371 protected void handleRefreshState(Object arg) {
372 handleUpdateState(mTmpState, arg);
373 final boolean changed = mTmpState.copyTo(mState);
374 if (changed) {
Fabian Kozynskica30f572019-10-24 10:01:04 -0400375 mQSLogger.logTileUpdated(mTileSpec, mState);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400376 handleStateChanged();
377 }
Jason Monk1c6116c2017-09-06 17:33:01 -0400378 mHandler.removeMessages(H.STALE);
379 mHandler.sendEmptyMessageDelayed(H.STALE, getStaleTimeout());
380 setListening(mStaleListener, false);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400381 }
382
383 private void handleStateChanged() {
Selim Cinek4fda7b22014-08-18 22:07:25 +0200384 boolean delayAnnouncement = shouldAnnouncementBeDelayed();
Jason Monkca894a02016-01-12 15:30:22 -0500385 if (mCallbacks.size() != 0) {
386 for (int i = 0; i < mCallbacks.size(); i++) {
387 mCallbacks.get(i).onStateChanged(mState);
388 }
Selim Cinek4fda7b22014-08-18 22:07:25 +0200389 if (mAnnounceNextStateChange && !delayAnnouncement) {
390 String announcement = composeChangeAnnouncement();
391 if (announcement != null) {
Jason Monkca894a02016-01-12 15:30:22 -0500392 mCallbacks.get(0).onAnnouncementRequested(announcement);
Selim Cinek4fda7b22014-08-18 22:07:25 +0200393 }
394 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400395 }
Selim Cinek4fda7b22014-08-18 22:07:25 +0200396 mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
397 }
398
399 protected boolean shouldAnnouncementBeDelayed() {
400 return false;
401 }
402
403 protected String composeChangeAnnouncement() {
404 return null;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400405 }
406
407 private void handleShowDetail(boolean show) {
Jason Monkbe3235a2017-04-05 09:29:53 -0400408 mShowingDetail = show;
Jason Monkca894a02016-01-12 15:30:22 -0500409 for (int i = 0; i < mCallbacks.size(); i++) {
410 mCallbacks.get(i).onShowDetail(show);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400411 }
412 }
413
Jason Monkbe3235a2017-04-05 09:29:53 -0400414 protected boolean isShowingDetail() {
415 return mShowingDetail;
416 }
417
John Spurlock7f8f22a2014-07-02 18:54:17 -0400418 private void handleToggleStateChanged(boolean state) {
Jason Monkca894a02016-01-12 15:30:22 -0500419 for (int i = 0; i < mCallbacks.size(); i++) {
420 mCallbacks.get(i).onToggleStateChanged(state);
John Spurlock7f8f22a2014-07-02 18:54:17 -0400421 }
422 }
423
John Spurlock486b78e2014-07-07 08:37:56 -0400424 private void handleScanStateChanged(boolean state) {
Jason Monkca894a02016-01-12 15:30:22 -0500425 for (int i = 0; i < mCallbacks.size(); i++) {
426 mCallbacks.get(i).onScanStateChanged(state);
John Spurlock486b78e2014-07-07 08:37:56 -0400427 }
428 }
429
John Spurlockaf8d6c42014-05-07 17:49:08 -0400430 protected void handleUserSwitch(int newUserId) {
431 handleRefreshState(null);
432 }
433
Amin Shaikh572230b2018-03-20 17:24:57 -0400434 private void handleSetListeningInternal(Object listener, boolean listening) {
Fabian Kozynskif4559512020-06-23 14:02:58 -0400435 // This should be used to go from resumed to paused. Listening for ON_RESUME and ON_PAUSE
436 // in this lifecycle will determine the listening window.
Amin Shaikh572230b2018-03-20 17:24:57 -0400437 if (listening) {
438 if (mListeners.add(listener) && mListeners.size() == 1) {
439 if (DEBUG) Log.d(TAG, "handleSetListening true");
Fabian Kozynskif4559512020-06-23 14:02:58 -0400440 mLifecycle.setCurrentState(RESUMED);
Amin Shaikh572230b2018-03-20 17:24:57 -0400441 handleSetListening(listening);
442 refreshState(); // Ensure we get at least one refresh after listening.
443 }
444 } else {
445 if (mListeners.remove(listener) && mListeners.size() == 0) {
446 if (DEBUG) Log.d(TAG, "handleSetListening false");
Fabian Kozynskif4559512020-06-23 14:02:58 -0400447 mLifecycle.setCurrentState(STARTED);
Amin Shaikh572230b2018-03-20 17:24:57 -0400448 handleSetListening(listening);
449 }
450 }
451 updateIsFullQs();
452 }
453
454 private void updateIsFullQs() {
455 for (Object listener : mListeners) {
456 if (TilePage.class.equals(listener.getClass())) {
457 mIsFullQs = 1;
458 return;
459 }
460 }
461 mIsFullQs = 0;
462 }
463
Fabian Kozynskica30f572019-10-24 10:01:04 -0400464 @CallSuper
465 protected void handleSetListening(boolean listening) {
466 if (mTileSpec != null) {
467 mQSLogger.logTileChangeListening(mTileSpec, listening);
468 }
469 }
Jason Monk1bec6af2016-05-31 15:40:58 -0400470
John Spurlockbceed062014-08-10 18:04:16 -0400471 protected void handleDestroy() {
Fabian Kozynskica30f572019-10-24 10:01:04 -0400472 mQSLogger.logTileDestroyed(mTileSpec, "Handle destroy");
Jason Monk794bcd22017-06-06 16:38:00 -0400473 if (mListeners.size() != 0) {
Jason Monk1c6116c2017-09-06 17:33:01 -0400474 handleSetListening(false);
Jason Monk794bcd22017-06-06 16:38:00 -0400475 }
Jason Monkca894a02016-01-12 15:30:22 -0500476 mCallbacks.clear();
Fabian Kozynski30aa3ad2019-12-12 15:12:37 -0500477 mHandler.removeCallbacksAndMessages(null);
John Spurlockbceed062014-08-10 18:04:16 -0400478 }
479
Sudheer Shankaa8fbbb32016-02-11 17:17:57 +0000480 protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
Philip P. Moltmann4e615e62018-08-28 14:57:49 -0700481 EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
Sudheer Shankaad143c02016-03-31 00:24:05 +0000482 userRestriction, ActivityManager.getCurrentUser());
Philip P. Moltmann4e615e62018-08-28 14:57:49 -0700483 if (admin != null && !RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext,
Sudheer Shankaad143c02016-03-31 00:24:05 +0000484 userRestriction, ActivityManager.getCurrentUser())) {
Sudheer Shanka1c7cda82015-12-31 14:46:02 +0000485 state.disabledByPolicy = true;
Jason Monk702e2eb2017-03-03 16:53:44 -0500486 mEnforcedAdmin = admin;
Sudheer Shanka1c7cda82015-12-31 14:46:02 +0000487 } else {
488 state.disabledByPolicy = false;
Jason Monk702e2eb2017-03-03 16:53:44 -0500489 mEnforcedAdmin = null;
Sudheer Shanka1c7cda82015-12-31 14:46:02 +0000490 }
491 }
492
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400493 @Override
494 public String getMetricsSpec() {
495 return mTileSpec;
496 }
497
Fabian Kozynski01f07682019-06-06 14:07:17 -0400498 /**
499 * Provides a default label for the tile.
500 * @return default label for the tile.
501 */
Jason Monk39c98e62016-03-16 09:18:35 -0400502 public abstract CharSequence getTileLabel();
503
Jason Monk32508852017-01-18 09:17:13 -0500504 public static int getColorForState(Context context, int state) {
505 switch (state) {
506 case Tile.STATE_UNAVAILABLE:
507 return Utils.getDisabled(context,
Jason Changb4e879d2018-04-11 11:17:58 +0800508 Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary));
Jason Monkb4cc7b12017-05-09 13:50:47 -0400509 case Tile.STATE_INACTIVE:
Jason Changb4e879d2018-04-11 11:17:58 +0800510 return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary);
Jason Monk32508852017-01-18 09:17:13 -0500511 case Tile.STATE_ACTIVE:
Fabian Kozynskie87ef4d2020-02-04 10:27:22 -0500512 return Utils.getColorAttrDefaultColor(context, android.R.attr.colorPrimary);
Jason Monk32508852017-01-18 09:17:13 -0500513 default:
514 Log.e("QSTile", "Invalid state " + state);
515 return 0;
516 }
517 }
518
John Spurlockaf8d6c42014-05-07 17:49:08 -0400519 protected final class H extends Handler {
Jason Monkca894a02016-01-12 15:30:22 -0500520 private static final int ADD_CALLBACK = 1;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400521 private static final int CLICK = 2;
522 private static final int SECONDARY_CLICK = 3;
John Spurlockc247b8f2014-11-06 23:06:25 -0500523 private static final int LONG_CLICK = 4;
524 private static final int REFRESH_STATE = 5;
525 private static final int SHOW_DETAIL = 6;
526 private static final int USER_SWITCH = 7;
527 private static final int TOGGLE_STATE_CHANGED = 8;
528 private static final int SCAN_STATE_CHANGED = 9;
529 private static final int DESTROY = 10;
Amin Shaikh299c45c2018-08-16 10:49:48 -0400530 private static final int REMOVE_CALLBACKS = 11;
531 private static final int REMOVE_CALLBACK = 12;
532 private static final int SET_LISTENING = 13;
533 private static final int STALE = 14;
John Spurlockaf8d6c42014-05-07 17:49:08 -0400534
Jason Monk1c6116c2017-09-06 17:33:01 -0400535 @VisibleForTesting
536 protected H(Looper looper) {
John Spurlockaf8d6c42014-05-07 17:49:08 -0400537 super(looper);
538 }
539
540 @Override
541 public void handleMessage(Message msg) {
542 String name = null;
543 try {
Jason Monkca894a02016-01-12 15:30:22 -0500544 if (msg.what == ADD_CALLBACK) {
545 name = "handleAddCallback";
Jason Monk9d02a432016-01-20 16:33:46 -0500546 handleAddCallback((QSTile.Callback) msg.obj);
547 } else if (msg.what == REMOVE_CALLBACKS) {
548 name = "handleRemoveCallbacks";
549 handleRemoveCallbacks();
Xiaohui Chen08e266c2016-04-18 12:53:28 -0700550 } else if (msg.what == REMOVE_CALLBACK) {
551 name = "handleRemoveCallback";
552 handleRemoveCallback((QSTile.Callback) msg.obj);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400553 } else if (msg.what == CLICK) {
554 name = "handleClick";
Sudheer Shanka1c7cda82015-12-31 14:46:02 +0000555 if (mState.disabledByPolicy) {
556 Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
Jason Monk702e2eb2017-03-03 16:53:44 -0500557 mContext, mEnforcedAdmin);
Jason Monk9c7844c2017-01-18 15:21:53 -0500558 Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(
559 intent, 0);
Sudheer Shanka1c7cda82015-12-31 14:46:02 +0000560 } else {
Sudheer Shanka1c7cda82015-12-31 14:46:02 +0000561 handleClick();
562 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400563 } else if (msg.what == SECONDARY_CLICK) {
564 name = "handleSecondaryClick";
565 handleSecondaryClick();
John Spurlockc247b8f2014-11-06 23:06:25 -0500566 } else if (msg.what == LONG_CLICK) {
567 name = "handleLongClick";
568 handleLongClick();
John Spurlockaf8d6c42014-05-07 17:49:08 -0400569 } else if (msg.what == REFRESH_STATE) {
570 name = "handleRefreshState";
571 handleRefreshState(msg.obj);
572 } else if (msg.what == SHOW_DETAIL) {
573 name = "handleShowDetail";
574 handleShowDetail(msg.arg1 != 0);
575 } else if (msg.what == USER_SWITCH) {
576 name = "handleUserSwitch";
577 handleUserSwitch(msg.arg1);
John Spurlock7f8f22a2014-07-02 18:54:17 -0400578 } else if (msg.what == TOGGLE_STATE_CHANGED) {
579 name = "handleToggleStateChanged";
580 handleToggleStateChanged(msg.arg1 != 0);
John Spurlock486b78e2014-07-07 08:37:56 -0400581 } else if (msg.what == SCAN_STATE_CHANGED) {
582 name = "handleScanStateChanged";
583 handleScanStateChanged(msg.arg1 != 0);
John Spurlockbceed062014-08-10 18:04:16 -0400584 } else if (msg.what == DESTROY) {
585 name = "handleDestroy";
586 handleDestroy();
Jason Monk1bec6af2016-05-31 15:40:58 -0400587 } else if (msg.what == SET_LISTENING) {
Amin Shaikh572230b2018-03-20 17:24:57 -0400588 name = "handleSetListeningInternal";
589 handleSetListeningInternal(msg.obj, msg.arg1 != 0);
Jason Monk1c6116c2017-09-06 17:33:01 -0400590 } else if (msg.what == STALE) {
591 name = "handleStale";
592 handleStale();
John Spurlockbceed062014-08-10 18:04:16 -0400593 } else {
594 throw new IllegalArgumentException("Unknown msg: " + msg.what);
John Spurlockaf8d6c42014-05-07 17:49:08 -0400595 }
596 } catch (Throwable t) {
597 final String error = "Error in " + name;
598 Log.w(TAG, error, t);
599 mHost.warn(error, t);
600 }
601 }
602 }
603
Jason Monk5db8a412015-10-21 15:16:23 -0700604 public static class DrawableIcon extends Icon {
605 protected final Drawable mDrawable;
Kensuke Matsui5a58cf92017-07-05 14:53:05 +0900606 protected final Drawable mInvisibleDrawable;
Jason Monk5db8a412015-10-21 15:16:23 -0700607
608 public DrawableIcon(Drawable drawable) {
609 mDrawable = drawable;
Kensuke Matsui5a58cf92017-07-05 14:53:05 +0900610 mInvisibleDrawable = drawable.getConstantState().newDrawable();
Jason Monk5db8a412015-10-21 15:16:23 -0700611 }
612
613 @Override
614 public Drawable getDrawable(Context context) {
615 return mDrawable;
616 }
Kensuke Matsui5a58cf92017-07-05 14:53:05 +0900617
618 @Override
619 public Drawable getInvisibleDrawable(Context context) {
620 return mInvisibleDrawable;
621 }
Fabian Kozynskica30f572019-10-24 10:01:04 -0400622
623 @Override
624 @NonNull
625 public String toString() {
626 return "DrawableIcon";
627 }
Jason Monkd2274f82016-12-12 12:02:16 -0500628 }
629
630 public static class DrawableIconWithRes extends DrawableIcon {
631 private final int mId;
632
633 public DrawableIconWithRes(Drawable drawable, int id) {
634 super(drawable);
635 mId = id;
636 }
Jason Monk1aec93f2016-03-01 09:39:30 -0500637
638 @Override
Jason Monkd2274f82016-12-12 12:02:16 -0500639 public boolean equals(Object o) {
640 return o instanceof DrawableIconWithRes && ((DrawableIconWithRes) o).mId == mId;
Jason Monk1aec93f2016-03-01 09:39:30 -0500641 }
Fabian Kozynskica30f572019-10-24 10:01:04 -0400642
643 @Override
644 @NonNull
645 public String toString() {
646 return String.format("DrawableIconWithRes[resId=0x%08x]", mId);
647 }
Jason Monk5db8a412015-10-21 15:16:23 -0700648 }
649
John Spurlock2d695812014-10-30 13:25:21 -0400650 public static class ResourceIcon extends Icon {
651 private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
652
Andrew Flynna478d702015-04-14 23:33:45 -0400653 protected final int mResId;
John Spurlock2d695812014-10-30 13:25:21 -0400654
655 private ResourceIcon(int resId) {
656 mResId = resId;
657 }
658
Fabian Kozynskifa1a1e22019-05-13 15:24:10 -0400659 public static synchronized Icon get(int resId) {
John Spurlock2d695812014-10-30 13:25:21 -0400660 Icon icon = ICONS.get(resId);
661 if (icon == null) {
662 icon = new ResourceIcon(resId);
663 ICONS.put(resId, icon);
664 }
665 return icon;
666 }
667
668 @Override
669 public Drawable getDrawable(Context context) {
Jason Monk66239fb2015-12-21 14:27:00 -0500670 return context.getDrawable(mResId);
John Spurlock2d695812014-10-30 13:25:21 -0400671 }
672
673 @Override
Jason Monk1aec93f2016-03-01 09:39:30 -0500674 public Drawable getInvisibleDrawable(Context context) {
675 return context.getDrawable(mResId);
676 }
677
678 @Override
John Spurlock2d695812014-10-30 13:25:21 -0400679 public boolean equals(Object o) {
680 return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;
681 }
682
683 @Override
Fabian Kozynskica30f572019-10-24 10:01:04 -0400684 @NonNull
John Spurlock2d695812014-10-30 13:25:21 -0400685 public String toString() {
686 return String.format("ResourceIcon[resId=0x%08x]", mResId);
687 }
688 }
689
Jason Monk1c6116c2017-09-06 17:33:01 -0400690 protected static class AnimationIcon extends ResourceIcon {
Jason Monk1aec93f2016-03-01 09:39:30 -0500691 private final int mAnimatedResId;
692
693 public AnimationIcon(int resId, int staticResId) {
694 super(staticResId);
695 mAnimatedResId = resId;
John Spurlock2d695812014-10-30 13:25:21 -0400696 }
697
John Spurlock2d695812014-10-30 13:25:21 -0400698 @Override
699 public Drawable getDrawable(Context context) {
700 // workaround: get a clean state for every new AVD
Jason Monk1aec93f2016-03-01 09:39:30 -0500701 return context.getDrawable(mAnimatedResId).getConstantState().newDrawable();
John Spurlock2d695812014-10-30 13:25:21 -0400702 }
Fabian Kozynskica30f572019-10-24 10:01:04 -0400703
704 @Override
705 @NonNull
706 public String toString() {
707 return String.format("AnimationIcon[resId=0x%08x]", mResId);
708 }
John Spurlock2d695812014-10-30 13:25:21 -0400709 }
Fabian Kozynski00d494d2019-04-04 09:53:50 -0400710
Fabian Kozynski05843f02019-06-28 13:19:57 -0400711 /**
712 * Dumps the state of this tile along with its name.
713 *
714 * This may be used for CTS testing of tiles.
715 */
Fabian Kozynski00d494d2019-04-04 09:53:50 -0400716 @Override
717 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
718 pw.println(this.getClass().getSimpleName() + ":");
719 pw.print(" "); pw.println(getState().toString());
720 }
John Spurlockaf8d6c42014-05-07 17:49:08 -0400721}