QSTile.java revision 30c305ce6283ce1380ad91ef0d221696b32d5a6b
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.Listenable; 34import com.android.systemui.statusbar.policy.LocationController; 35import com.android.systemui.statusbar.policy.NetworkController; 36import com.android.systemui.statusbar.policy.RotationLockController; 37import com.android.systemui.statusbar.policy.TetheringController; 38import com.android.systemui.statusbar.policy.ZenModeController; 39import com.android.systemui.volume.VolumeComponent; 40 41import java.util.List; 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 = false; 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 View createDetailView(Context context, ViewGroup root) { 87 return null; // optional 88 } 89 90 // safe to call from any thread 91 92 public void setCallback(Callback callback) { 93 mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget(); 94 } 95 96 public void click() { 97 mHandler.sendEmptyMessage(H.CLICK); 98 } 99 100 public void secondaryClick() { 101 mHandler.sendEmptyMessage(H.SECONDARY_CLICK); 102 } 103 104 public void showDetail(boolean show) { 105 mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget(); 106 } 107 108 protected final void refreshState() { 109 refreshState(null); 110 } 111 112 protected final void refreshState(Object arg) { 113 mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget(); 114 } 115 116 public void userSwitch(int newUserId) { 117 mHandler.obtainMessage(H.USER_SWITCH, newUserId).sendToTarget(); 118 } 119 120 // call only on tile worker looper 121 122 private void handleSetCallback(Callback callback) { 123 mCallback = callback; 124 handleRefreshState(null); 125 } 126 127 protected void handleSecondaryClick() { 128 // optional 129 } 130 131 protected void handleRefreshState(Object arg) { 132 handleUpdateState(mTmpState, arg); 133 final boolean changed = mTmpState.copyTo(mState); 134 if (changed) { 135 handleStateChanged(); 136 } 137 } 138 139 private void handleStateChanged() { 140 if (mCallback != null) { 141 mCallback.onStateChanged(mState); 142 } 143 } 144 145 private void handleShowDetail(boolean show) { 146 if (mCallback != null) { 147 mCallback.onShowDetail(show); 148 } 149 } 150 151 protected void handleUserSwitch(int newUserId) { 152 handleRefreshState(null); 153 } 154 155 protected final class H extends Handler { 156 private static final int SET_CALLBACK = 1; 157 private static final int CLICK = 2; 158 private static final int SECONDARY_CLICK = 3; 159 private static final int REFRESH_STATE = 4; 160 private static final int SHOW_DETAIL = 5; 161 private static final int USER_SWITCH = 6; 162 163 private H(Looper looper) { 164 super(looper); 165 } 166 167 @Override 168 public void handleMessage(Message msg) { 169 String name = null; 170 try { 171 if (msg.what == SET_CALLBACK) { 172 name = "handleSetCallback"; 173 handleSetCallback((QSTile.Callback)msg.obj); 174 } else if (msg.what == CLICK) { 175 name = "handleClick"; 176 handleClick(); 177 } else if (msg.what == SECONDARY_CLICK) { 178 name = "handleSecondaryClick"; 179 handleSecondaryClick(); 180 } else if (msg.what == REFRESH_STATE) { 181 name = "handleRefreshState"; 182 handleRefreshState(msg.obj); 183 } else if (msg.what == SHOW_DETAIL) { 184 name = "handleShowDetail"; 185 handleShowDetail(msg.arg1 != 0); 186 } else if (msg.what == USER_SWITCH) { 187 name = "handleUserSwitch"; 188 handleUserSwitch(msg.arg1); 189 } 190 } catch (Throwable t) { 191 final String error = "Error in " + name; 192 Log.w(TAG, error, t); 193 mHost.warn(error, t); 194 } 195 } 196 } 197 198 public interface Callback { 199 void onStateChanged(State state); 200 void onShowDetail(boolean show); 201 } 202 203 public interface Host { 204 void startSettingsActivity(Intent intent); 205 void warn(String message, Throwable t); 206 void collapsePanels(); 207 Looper getLooper(); 208 Context getContext(); 209 BluetoothController getBluetoothController(); 210 LocationController getLocationController(); 211 RotationLockController getRotationLockController(); 212 List<QSTile<?>> getTiles(); 213 NetworkController getNetworkController(); 214 ZenModeController getZenModeController(); 215 TetheringController getTetheringController(); 216 CastController getCastController(); 217 VolumeComponent getVolumeComponent(); 218 FlashlightController getFlashlightController(); 219 } 220 221 public static class State { 222 public boolean visible; 223 public int iconId; 224 public Drawable icon; 225 public String label; 226 public String contentDescription; 227 228 public boolean copyTo(State other) { 229 if (other == null) throw new IllegalArgumentException(); 230 if (!other.getClass().equals(getClass())) throw new IllegalArgumentException(); 231 final boolean changed = other.visible != visible 232 || other.iconId != iconId 233 || !Objects.equals(other.icon, icon) 234 || !Objects.equals(other.label, label) 235 || !Objects.equals(other.contentDescription, contentDescription); 236 other.visible = visible; 237 other.iconId = iconId; 238 other.icon = icon; 239 other.label = label; 240 other.contentDescription = contentDescription; 241 return changed; 242 } 243 244 @Override 245 public String toString() { 246 return toStringBuilder().toString(); 247 } 248 249 protected StringBuilder toStringBuilder() { 250 final StringBuilder sb = new StringBuilder( getClass().getSimpleName()).append('['); 251 sb.append("visible=").append(visible); 252 sb.append(",iconId=").append(iconId); 253 sb.append(",icon=").append(icon); 254 sb.append(",label=").append(label); 255 sb.append(",contentDescription=").append(contentDescription); 256 return sb.append(']'); 257 } 258 } 259 260 public static class BooleanState extends State { 261 public boolean value; 262 263 @Override 264 public boolean copyTo(State other) { 265 final BooleanState o = (BooleanState) other; 266 final boolean changed = super.copyTo(other) || o.value != value; 267 o.value = value; 268 return changed; 269 } 270 271 @Override 272 protected StringBuilder toStringBuilder() { 273 final StringBuilder rt = super.toStringBuilder(); 274 rt.insert(rt.length() - 1, ",value=" + value); 275 return rt; 276 } 277 } 278 279 public static final class SignalState extends State { 280 public boolean enabled; 281 public boolean connected; 282 public boolean activityIn; 283 public boolean activityOut; 284 public int overlayIconId; 285 public boolean filter; 286 287 @Override 288 public boolean copyTo(State other) { 289 final SignalState o = (SignalState) other; 290 final boolean changed = o.enabled != enabled 291 || o.connected != connected || o.activityIn != activityIn 292 || o.activityOut != activityOut 293 || o.overlayIconId != overlayIconId; 294 o.enabled = enabled; 295 o.connected = connected; 296 o.activityIn = activityIn; 297 o.activityOut = activityOut; 298 o.overlayIconId = overlayIconId; 299 o.filter = filter; 300 return super.copyTo(other) || changed; 301 } 302 303 @Override 304 protected StringBuilder toStringBuilder() { 305 final StringBuilder rt = super.toStringBuilder(); 306 rt.insert(rt.length() - 1, ",enabled=" + enabled); 307 rt.insert(rt.length() - 1, ",connected=" + connected); 308 rt.insert(rt.length() - 1, ",activityIn=" + activityIn); 309 rt.insert(rt.length() - 1, ",activityOut=" + activityOut); 310 rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId); 311 rt.insert(rt.length() - 1, ",filter=" + filter); 312 return rt; 313 } 314 } 315} 316