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