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