Home | History | Annotate | Download | only in qs
      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 
     17 package com.android.systemui.qs;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.graphics.drawable.Drawable;
     22 import android.os.Handler;
     23 import android.os.Looper;
     24 import android.os.Message;
     25 import android.util.Log;
     26 import android.view.View;
     27 import android.view.ViewGroup;
     28 
     29 import com.android.systemui.qs.QSTile.State;
     30 import com.android.systemui.statusbar.policy.BluetoothController;
     31 import com.android.systemui.statusbar.policy.CastController;
     32 import com.android.systemui.statusbar.policy.FlashlightController;
     33 import com.android.systemui.statusbar.policy.KeyguardMonitor;
     34 import com.android.systemui.statusbar.policy.Listenable;
     35 import com.android.systemui.statusbar.policy.LocationController;
     36 import com.android.systemui.statusbar.policy.NetworkController;
     37 import com.android.systemui.statusbar.policy.RotationLockController;
     38 import com.android.systemui.statusbar.policy.HotspotController;
     39 import com.android.systemui.statusbar.policy.ZenModeController;
     40 
     41 import java.util.Collection;
     42 import 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  */
     51 public 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     private boolean mAnnounceNextStateChange;
     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, 0).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     public void destroy() {
    138         mHandler.sendEmptyMessage(H.DESTROY);
    139     }
    140 
    141     public TState getState() {
    142         return mState;
    143     }
    144 
    145     // call only on tile worker looper
    146 
    147     private void handleSetCallback(Callback callback) {
    148         mCallback = callback;
    149         handleRefreshState(null);
    150     }
    151 
    152     protected void handleSecondaryClick() {
    153         // optional
    154     }
    155 
    156     protected void handleRefreshState(Object arg) {
    157         handleUpdateState(mTmpState, arg);
    158         final boolean changed = mTmpState.copyTo(mState);
    159         if (changed) {
    160             handleStateChanged();
    161         }
    162     }
    163 
    164     private void handleStateChanged() {
    165         boolean delayAnnouncement = shouldAnnouncementBeDelayed();
    166         if (mCallback != null) {
    167             mCallback.onStateChanged(mState);
    168             if (mAnnounceNextStateChange && !delayAnnouncement) {
    169                 String announcement = composeChangeAnnouncement();
    170                 if (announcement != null) {
    171                     mCallback.onAnnouncementRequested(announcement);
    172                 }
    173             }
    174         }
    175         mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
    176     }
    177 
    178     protected boolean shouldAnnouncementBeDelayed() {
    179         return false;
    180     }
    181 
    182     protected String composeChangeAnnouncement() {
    183         return null;
    184     }
    185 
    186     private void handleShowDetail(boolean show) {
    187         if (mCallback != null) {
    188             mCallback.onShowDetail(show);
    189         }
    190     }
    191 
    192     private void handleToggleStateChanged(boolean state) {
    193         if (mCallback != null) {
    194             mCallback.onToggleStateChanged(state);
    195         }
    196     }
    197 
    198     private void handleScanStateChanged(boolean state) {
    199         if (mCallback != null) {
    200             mCallback.onScanStateChanged(state);
    201         }
    202     }
    203 
    204     protected void handleUserSwitch(int newUserId) {
    205         handleRefreshState(null);
    206     }
    207 
    208     protected void handleDestroy() {
    209         setListening(false);
    210         mCallback = null;
    211     }
    212 
    213     protected final class H extends Handler {
    214         private static final int SET_CALLBACK = 1;
    215         private static final int CLICK = 2;
    216         private static final int SECONDARY_CLICK = 3;
    217         private static final int REFRESH_STATE = 4;
    218         private static final int SHOW_DETAIL = 5;
    219         private static final int USER_SWITCH = 6;
    220         private static final int TOGGLE_STATE_CHANGED = 7;
    221         private static final int SCAN_STATE_CHANGED = 8;
    222         private static final int DESTROY = 9;
    223 
    224         private H(Looper looper) {
    225             super(looper);
    226         }
    227 
    228         @Override
    229         public void handleMessage(Message msg) {
    230             String name = null;
    231             try {
    232                 if (msg.what == SET_CALLBACK) {
    233                     name = "handleSetCallback";
    234                     handleSetCallback((QSTile.Callback)msg.obj);
    235                 } else if (msg.what == CLICK) {
    236                     name = "handleClick";
    237                     mAnnounceNextStateChange = true;
    238                     handleClick();
    239                 } else if (msg.what == SECONDARY_CLICK) {
    240                     name = "handleSecondaryClick";
    241                     handleSecondaryClick();
    242                 } else if (msg.what == REFRESH_STATE) {
    243                     name = "handleRefreshState";
    244                     handleRefreshState(msg.obj);
    245                 } else if (msg.what == SHOW_DETAIL) {
    246                     name = "handleShowDetail";
    247                     handleShowDetail(msg.arg1 != 0);
    248                 } else if (msg.what == USER_SWITCH) {
    249                     name = "handleUserSwitch";
    250                     handleUserSwitch(msg.arg1);
    251                 } else if (msg.what == TOGGLE_STATE_CHANGED) {
    252                     name = "handleToggleStateChanged";
    253                     handleToggleStateChanged(msg.arg1 != 0);
    254                 } else if (msg.what == SCAN_STATE_CHANGED) {
    255                     name = "handleScanStateChanged";
    256                     handleScanStateChanged(msg.arg1 != 0);
    257                 } else if (msg.what == DESTROY) {
    258                     name = "handleDestroy";
    259                     handleDestroy();
    260                 } else {
    261                     throw new IllegalArgumentException("Unknown msg: " + msg.what);
    262                 }
    263             } catch (Throwable t) {
    264                 final String error = "Error in " + name;
    265                 Log.w(TAG, error, t);
    266                 mHost.warn(error, t);
    267             }
    268         }
    269     }
    270 
    271     public interface Callback {
    272         void onStateChanged(State state);
    273         void onShowDetail(boolean show);
    274         void onToggleStateChanged(boolean state);
    275         void onScanStateChanged(boolean state);
    276         void onAnnouncementRequested(CharSequence announcement);
    277     }
    278 
    279     public interface Host {
    280         void startSettingsActivity(Intent intent);
    281         void warn(String message, Throwable t);
    282         void collapsePanels();
    283         Looper getLooper();
    284         Context getContext();
    285         Collection<QSTile<?>> getTiles();
    286         void setCallback(Callback callback);
    287         BluetoothController getBluetoothController();
    288         LocationController getLocationController();
    289         RotationLockController getRotationLockController();
    290         NetworkController getNetworkController();
    291         ZenModeController getZenModeController();
    292         HotspotController getHotspotController();
    293         CastController getCastController();
    294         FlashlightController getFlashlightController();
    295         KeyguardMonitor getKeyguardMonitor();
    296 
    297         public interface Callback {
    298             void onTilesChanged();
    299         }
    300     }
    301 
    302     public static class State {
    303         public boolean visible;
    304         public int iconId;
    305         public Drawable icon;
    306         public String label;
    307         public String contentDescription;
    308         public String dualLabelContentDescription;
    309         public boolean autoMirrorDrawable = true;
    310 
    311         public boolean copyTo(State other) {
    312             if (other == null) throw new IllegalArgumentException();
    313             if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
    314             final boolean changed = other.visible != visible
    315                     || other.iconId != iconId
    316                     || !Objects.equals(other.icon, icon)
    317                     || !Objects.equals(other.label, label)
    318                     || !Objects.equals(other.contentDescription, contentDescription)
    319                     || !Objects.equals(other.autoMirrorDrawable, autoMirrorDrawable)
    320                     || !Objects.equals(other.dualLabelContentDescription,
    321                     dualLabelContentDescription);
    322             other.visible = visible;
    323             other.iconId = iconId;
    324             other.icon = icon;
    325             other.label = label;
    326             other.contentDescription = contentDescription;
    327             other.dualLabelContentDescription = dualLabelContentDescription;
    328             other.autoMirrorDrawable = autoMirrorDrawable;
    329             return changed;
    330         }
    331 
    332         @Override
    333         public String toString() {
    334             return toStringBuilder().toString();
    335         }
    336 
    337         protected StringBuilder toStringBuilder() {
    338             final StringBuilder sb = new StringBuilder(  getClass().getSimpleName()).append('[');
    339             sb.append("visible=").append(visible);
    340             sb.append(",iconId=").append(iconId);
    341             sb.append(",icon=").append(icon);
    342             sb.append(",label=").append(label);
    343             sb.append(",contentDescription=").append(contentDescription);
    344             sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
    345             sb.append(",autoMirrorDrawable=").append(autoMirrorDrawable);
    346             return sb.append(']');
    347         }
    348     }
    349 
    350     public static class BooleanState extends State {
    351         public boolean value;
    352 
    353         @Override
    354         public boolean copyTo(State other) {
    355             final BooleanState o = (BooleanState) other;
    356             final boolean changed = super.copyTo(other) || o.value != value;
    357             o.value = value;
    358             return changed;
    359         }
    360 
    361         @Override
    362         protected StringBuilder toStringBuilder() {
    363             final StringBuilder rt = super.toStringBuilder();
    364             rt.insert(rt.length() - 1, ",value=" + value);
    365             return rt;
    366         }
    367     }
    368 
    369     public static final class SignalState extends State {
    370         public boolean enabled;
    371         public boolean connected;
    372         public boolean activityIn;
    373         public boolean activityOut;
    374         public int overlayIconId;
    375         public boolean filter;
    376         public boolean isOverlayIconWide;
    377 
    378         @Override
    379         public boolean copyTo(State other) {
    380             final SignalState o = (SignalState) other;
    381             final boolean changed = o.enabled != enabled
    382                     || o.connected != connected || o.activityIn != activityIn
    383                     || o.activityOut != activityOut
    384                     || o.overlayIconId != overlayIconId
    385                     || o.isOverlayIconWide != isOverlayIconWide;
    386             o.enabled = enabled;
    387             o.connected = connected;
    388             o.activityIn = activityIn;
    389             o.activityOut = activityOut;
    390             o.overlayIconId = overlayIconId;
    391             o.filter = filter;
    392             o.isOverlayIconWide = isOverlayIconWide;
    393             return super.copyTo(other) || changed;
    394         }
    395 
    396         @Override
    397         protected StringBuilder toStringBuilder() {
    398             final StringBuilder rt = super.toStringBuilder();
    399             rt.insert(rt.length() - 1, ",enabled=" + enabled);
    400             rt.insert(rt.length() - 1, ",connected=" + connected);
    401             rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
    402             rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
    403             rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
    404             rt.insert(rt.length() - 1, ",filter=" + filter);
    405             rt.insert(rt.length() - 1, ",wideOverlayIcon=" + isOverlayIconWide);
    406             return rt;
    407         }
    408     }
    409 }
    410