QSTile.java revision e5557a972ca190cb82026a5dd0c53f4d119fa05a
1/* 2 * Copyright (C) 2014 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 17package com.android.systemui.qs; 18 19import android.content.Context; 20import android.content.Intent; 21import android.graphics.drawable.Drawable; 22import android.os.Handler; 23import android.os.Looper; 24import android.os.Message; 25import android.util.Log; 26import android.view.View; 27import android.view.ViewGroup; 28 29import com.android.systemui.qs.QSTile.State; 30import com.android.systemui.statusbar.policy.BluetoothController; 31import com.android.systemui.statusbar.policy.CastController; 32import com.android.systemui.statusbar.policy.FlashlightController; 33import com.android.systemui.statusbar.policy.KeyguardMonitor; 34import com.android.systemui.statusbar.policy.Listenable; 35import com.android.systemui.statusbar.policy.LocationController; 36import com.android.systemui.statusbar.policy.NetworkController; 37import com.android.systemui.statusbar.policy.RotationLockController; 38import com.android.systemui.statusbar.policy.HotspotController; 39import com.android.systemui.statusbar.policy.ZenModeController; 40 41import java.util.Collection; 42import java.util.Objects; 43 44/** 45 * Base quick-settings tile, extend this to create a new tile. 46 * 47 * State management done on a looper provided by the host. Tiles should update state in 48 * handleUpdateState. Callbacks affecting state should use refreshState to trigger another 49 * state update pass on tile looper. 50 */ 51public abstract class QSTile<TState extends State> implements Listenable { 52 protected final String TAG = "QSTile." + getClass().getSimpleName(); 53 protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG); 54 55 protected final Host mHost; 56 protected final Context mContext; 57 protected final H mHandler; 58 protected final Handler mUiHandler = new Handler(Looper.getMainLooper()); 59 60 private Callback mCallback; 61 protected final TState mState = newTileState(); 62 private final TState mTmpState = newTileState(); 63 64 abstract protected TState newTileState(); 65 abstract protected void handleClick(); 66 abstract protected void handleUpdateState(TState state, Object arg); 67 68 protected QSTile(Host host) { 69 mHost = host; 70 mContext = host.getContext(); 71 mHandler = new H(host.getLooper()); 72 } 73 74 public boolean supportsDualTargets() { 75 return false; 76 } 77 78 public Host getHost() { 79 return mHost; 80 } 81 82 public QSTileView createTileView(Context context) { 83 return new QSTileView(context); 84 } 85 86 public DetailAdapter getDetailAdapter() { 87 return null; // optional 88 } 89 90 public interface DetailAdapter { 91 int getTitle(); 92 Boolean getToggleState(); 93 View createDetailView(Context context, View convertView, ViewGroup parent); 94 Intent getSettingsIntent(); 95 void setToggleState(boolean state); 96 } 97 98 // safe to call from any thread 99 100 public void setCallback(Callback callback) { 101 mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget(); 102 } 103 104 public void click() { 105 mHandler.sendEmptyMessage(H.CLICK); 106 } 107 108 public void secondaryClick() { 109 mHandler.sendEmptyMessage(H.SECONDARY_CLICK); 110 } 111 112 public void showDetail(boolean show) { 113 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget(); 114 } 115 116 protected final void refreshState() { 117 refreshState(null); 118 } 119 120 protected final void refreshState(Object arg) { 121 mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget(); 122 } 123 124 public void userSwitch(int newUserId) { 125 mHandler.obtainMessage(H.USER_SWITCH, newUserId).sendToTarget(); 126 } 127 128 public void fireToggleStateChanged(boolean state) { 129 mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget(); 130 } 131 132 public void fireScanStateChanged(boolean state) { 133 mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget(); 134 } 135 136 public void destroy() { 137 mHandler.sendEmptyMessage(H.DESTROY); 138 } 139 140 public TState getState() { 141 return mState; 142 } 143 144 // call only on tile worker looper 145 146 private void handleSetCallback(Callback callback) { 147 mCallback = callback; 148 handleRefreshState(null); 149 } 150 151 protected void handleSecondaryClick() { 152 // optional 153 } 154 155 protected void handleRefreshState(Object arg) { 156 handleUpdateState(mTmpState, arg); 157 final boolean changed = mTmpState.copyTo(mState); 158 if (changed) { 159 handleStateChanged(); 160 } 161 } 162 163 private void handleStateChanged() { 164 if (mCallback != null) { 165 mCallback.onStateChanged(mState); 166 } 167 } 168 169 private void handleShowDetail(boolean show) { 170 if (mCallback != null) { 171 mCallback.onShowDetail(show); 172 } 173 } 174 175 private void handleToggleStateChanged(boolean state) { 176 if (mCallback != null) { 177 mCallback.onToggleStateChanged(state); 178 } 179 } 180 181 private void handleScanStateChanged(boolean state) { 182 if (mCallback != null) { 183 mCallback.onScanStateChanged(state); 184 } 185 } 186 187 protected void handleUserSwitch(int newUserId) { 188 handleRefreshState(null); 189 } 190 191 protected void handleDestroy() { 192 setListening(false); 193 mCallback = null; 194 } 195 196 protected final class H extends Handler { 197 private static final int SET_CALLBACK = 1; 198 private static final int CLICK = 2; 199 private static final int SECONDARY_CLICK = 3; 200 private static final int REFRESH_STATE = 4; 201 private static final int SHOW_DETAIL = 5; 202 private static final int USER_SWITCH = 6; 203 private static final int TOGGLE_STATE_CHANGED = 7; 204 private static final int SCAN_STATE_CHANGED = 8; 205 private static final int DESTROY = 9; 206 207 private H(Looper looper) { 208 super(looper); 209 } 210 211 @Override 212 public void handleMessage(Message msg) { 213 String name = null; 214 try { 215 if (msg.what == SET_CALLBACK) { 216 name = "handleSetCallback"; 217 handleSetCallback((QSTile.Callback)msg.obj); 218 } else if (msg.what == CLICK) { 219 name = "handleClick"; 220 handleClick(); 221 } else if (msg.what == SECONDARY_CLICK) { 222 name = "handleSecondaryClick"; 223 handleSecondaryClick(); 224 } else if (msg.what == REFRESH_STATE) { 225 name = "handleRefreshState"; 226 handleRefreshState(msg.obj); 227 } else if (msg.what == SHOW_DETAIL) { 228 name = "handleShowDetail"; 229 handleShowDetail(msg.arg1 != 0); 230 } else if (msg.what == USER_SWITCH) { 231 name = "handleUserSwitch"; 232 handleUserSwitch(msg.arg1); 233 } else if (msg.what == TOGGLE_STATE_CHANGED) { 234 name = "handleToggleStateChanged"; 235 handleToggleStateChanged(msg.arg1 != 0); 236 } else if (msg.what == SCAN_STATE_CHANGED) { 237 name = "handleScanStateChanged"; 238 handleScanStateChanged(msg.arg1 != 0); 239 } else if (msg.what == DESTROY) { 240 name = "handleDestroy"; 241 handleDestroy(); 242 } else { 243 throw new IllegalArgumentException("Unknown msg: " + msg.what); 244 } 245 } catch (Throwable t) { 246 final String error = "Error in " + name; 247 Log.w(TAG, error, t); 248 mHost.warn(error, t); 249 } 250 } 251 } 252 253 public interface Callback { 254 void onStateChanged(State state); 255 void onShowDetail(boolean show); 256 void onToggleStateChanged(boolean state); 257 void onScanStateChanged(boolean state); 258 } 259 260 public interface Host { 261 void startSettingsActivity(Intent intent); 262 void warn(String message, Throwable t); 263 void collapsePanels(); 264 Looper getLooper(); 265 Context getContext(); 266 Collection<QSTile<?>> getTiles(); 267 void setCallback(Callback callback); 268 BluetoothController getBluetoothController(); 269 LocationController getLocationController(); 270 RotationLockController getRotationLockController(); 271 NetworkController getNetworkController(); 272 ZenModeController getZenModeController(); 273 HotspotController getHotspotController(); 274 CastController getCastController(); 275 FlashlightController getFlashlightController(); 276 KeyguardMonitor getKeyguardMonitor(); 277 278 public interface Callback { 279 void onTilesChanged(); 280 } 281 } 282 283 public static class State { 284 public boolean visible; 285 public int iconId; 286 public Drawable icon; 287 public String label; 288 public String contentDescription; 289 public String dualLabelContentDescription; 290 291 public boolean copyTo(State other) { 292 if (other == null) throw new IllegalArgumentException(); 293 if (!other.getClass().equals(getClass())) throw new IllegalArgumentException(); 294 final boolean changed = other.visible != visible 295 || other.iconId != iconId 296 || !Objects.equals(other.icon, icon) 297 || !Objects.equals(other.label, label) 298 || !Objects.equals(other.contentDescription, contentDescription) 299 || !Objects.equals(other.dualLabelContentDescription, 300 dualLabelContentDescription); 301 other.visible = visible; 302 other.iconId = iconId; 303 other.icon = icon; 304 other.label = label; 305 other.contentDescription = contentDescription; 306 other.dualLabelContentDescription = dualLabelContentDescription; 307 return changed; 308 } 309 310 @Override 311 public String toString() { 312 return toStringBuilder().toString(); 313 } 314 315 protected StringBuilder toStringBuilder() { 316 final StringBuilder sb = new StringBuilder( getClass().getSimpleName()).append('['); 317 sb.append("visible=").append(visible); 318 sb.append(",iconId=").append(iconId); 319 sb.append(",icon=").append(icon); 320 sb.append(",label=").append(label); 321 sb.append(",contentDescription=").append(contentDescription); 322 sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription); 323 return sb.append(']'); 324 } 325 } 326 327 public static class BooleanState extends State { 328 public boolean value; 329 330 @Override 331 public boolean copyTo(State other) { 332 final BooleanState o = (BooleanState) other; 333 final boolean changed = super.copyTo(other) || o.value != value; 334 o.value = value; 335 return changed; 336 } 337 338 @Override 339 protected StringBuilder toStringBuilder() { 340 final StringBuilder rt = super.toStringBuilder(); 341 rt.insert(rt.length() - 1, ",value=" + value); 342 return rt; 343 } 344 } 345 346 public static final class SignalState extends State { 347 public boolean enabled; 348 public boolean connected; 349 public boolean activityIn; 350 public boolean activityOut; 351 public int overlayIconId; 352 public boolean filter; 353 354 @Override 355 public boolean copyTo(State other) { 356 final SignalState o = (SignalState) other; 357 final boolean changed = o.enabled != enabled 358 || o.connected != connected || o.activityIn != activityIn 359 || o.activityOut != activityOut 360 || o.overlayIconId != overlayIconId; 361 o.enabled = enabled; 362 o.connected = connected; 363 o.activityIn = activityIn; 364 o.activityOut = activityOut; 365 o.overlayIconId = overlayIconId; 366 o.filter = filter; 367 return super.copyTo(other) || changed; 368 } 369 370 @Override 371 protected StringBuilder toStringBuilder() { 372 final StringBuilder rt = super.toStringBuilder(); 373 rt.insert(rt.length() - 1, ",enabled=" + enabled); 374 rt.insert(rt.length() - 1, ",connected=" + connected); 375 rt.insert(rt.length() - 1, ",activityIn=" + activityIn); 376 rt.insert(rt.length() - 1, ",activityOut=" + activityOut); 377 rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId); 378 rt.insert(rt.length() - 1, ",filter=" + filter); 379 return rt; 380 } 381 } 382} 383