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.Animatable;
     22 import android.graphics.drawable.AnimatedVectorDrawable;
     23 import android.graphics.drawable.Drawable;
     24 import android.os.Handler;
     25 import android.os.Looper;
     26 import android.os.Message;
     27 import android.util.Log;
     28 import android.util.SparseArray;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 
     32 import com.android.systemui.qs.QSTile.State;
     33 import com.android.systemui.statusbar.policy.BluetoothController;
     34 import com.android.systemui.statusbar.policy.CastController;
     35 import com.android.systemui.statusbar.policy.FlashlightController;
     36 import com.android.systemui.statusbar.policy.HotspotController;
     37 import com.android.systemui.statusbar.policy.KeyguardMonitor;
     38 import com.android.systemui.statusbar.policy.Listenable;
     39 import com.android.systemui.statusbar.policy.LocationController;
     40 import com.android.systemui.statusbar.policy.NetworkController;
     41 import com.android.systemui.statusbar.policy.RotationLockController;
     42 import com.android.systemui.statusbar.policy.ZenModeController;
     43 
     44 import java.util.Collection;
     45 import java.util.Objects;
     46 
     47 /**
     48  * Base quick-settings tile, extend this to create a new tile.
     49  *
     50  * State management done on a looper provided by the host.  Tiles should update state in
     51  * handleUpdateState.  Callbacks affecting state should use refreshState to trigger another
     52  * state update pass on tile looper.
     53  */
     54 public abstract class QSTile<TState extends State> implements Listenable {
     55     protected final String TAG = "QSTile." + getClass().getSimpleName();
     56     protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG);
     57 
     58     protected final Host mHost;
     59     protected final Context mContext;
     60     protected final H mHandler;
     61     protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
     62 
     63     private Callback mCallback;
     64     protected TState mState = newTileState();
     65     private TState mTmpState = newTileState();
     66     private boolean mAnnounceNextStateChange;
     67 
     68     abstract protected TState newTileState();
     69     abstract protected void handleClick();
     70     abstract protected void handleUpdateState(TState state, Object arg);
     71 
     72     /**
     73      * Declare the category of this tile.
     74      *
     75      * Categories are defined in {@link com.android.internal.logging.MetricsLogger}
     76      * or if there is no relevant existing category you may define one in
     77      * {@link com.android.systemui.qs.QSTile}.
     78      */
     79     abstract public int getMetricsCategory();
     80 
     81     protected QSTile(Host host) {
     82         mHost = host;
     83         mContext = host.getContext();
     84         mHandler = new H(host.getLooper());
     85     }
     86 
     87     public boolean supportsDualTargets() {
     88         return false;
     89     }
     90 
     91     public Host getHost() {
     92         return mHost;
     93     }
     94 
     95     public QSTileView createTileView(Context context) {
     96         return new QSTileView(context);
     97     }
     98 
     99     public DetailAdapter getDetailAdapter() {
    100         return null; // optional
    101     }
    102 
    103     public interface DetailAdapter {
    104         int getTitle();
    105         Boolean getToggleState();
    106         View createDetailView(Context context, View convertView, ViewGroup parent);
    107         Intent getSettingsIntent();
    108         void setToggleState(boolean state);
    109         int getMetricsCategory();
    110     }
    111 
    112     // safe to call from any thread
    113 
    114     public void setCallback(Callback callback) {
    115         mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
    116     }
    117 
    118     public void click() {
    119         mHandler.sendEmptyMessage(H.CLICK);
    120     }
    121 
    122     public void secondaryClick() {
    123         mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
    124     }
    125 
    126     public void longClick() {
    127         mHandler.sendEmptyMessage(H.LONG_CLICK);
    128     }
    129 
    130     public void showDetail(boolean show) {
    131         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
    132     }
    133 
    134     protected final void refreshState() {
    135         refreshState(null);
    136     }
    137 
    138     protected final void refreshState(Object arg) {
    139         mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
    140     }
    141 
    142     public final void clearState() {
    143         mHandler.sendEmptyMessage(H.CLEAR_STATE);
    144     }
    145 
    146     public void userSwitch(int newUserId) {
    147         mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
    148     }
    149 
    150     public void fireToggleStateChanged(boolean state) {
    151         mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
    152     }
    153 
    154     public void fireScanStateChanged(boolean state) {
    155         mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
    156     }
    157 
    158     public void destroy() {
    159         mHandler.sendEmptyMessage(H.DESTROY);
    160     }
    161 
    162     public TState getState() {
    163         return mState;
    164     }
    165 
    166     public void setDetailListening(boolean listening) {
    167         // optional
    168     }
    169 
    170     // call only on tile worker looper
    171 
    172     private void handleSetCallback(Callback callback) {
    173         mCallback = callback;
    174         handleRefreshState(null);
    175     }
    176 
    177     protected void handleSecondaryClick() {
    178         // optional
    179     }
    180 
    181     protected void handleLongClick() {
    182         // optional
    183     }
    184 
    185     protected void handleClearState() {
    186         mTmpState = newTileState();
    187         mState = newTileState();
    188     }
    189 
    190     protected void handleRefreshState(Object arg) {
    191         handleUpdateState(mTmpState, arg);
    192         final boolean changed = mTmpState.copyTo(mState);
    193         if (changed) {
    194             handleStateChanged();
    195         }
    196     }
    197 
    198     private void handleStateChanged() {
    199         boolean delayAnnouncement = shouldAnnouncementBeDelayed();
    200         if (mCallback != null) {
    201             mCallback.onStateChanged(mState);
    202             if (mAnnounceNextStateChange && !delayAnnouncement) {
    203                 String announcement = composeChangeAnnouncement();
    204                 if (announcement != null) {
    205                     mCallback.onAnnouncementRequested(announcement);
    206                 }
    207             }
    208         }
    209         mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
    210     }
    211 
    212     protected boolean shouldAnnouncementBeDelayed() {
    213         return false;
    214     }
    215 
    216     protected String composeChangeAnnouncement() {
    217         return null;
    218     }
    219 
    220     private void handleShowDetail(boolean show) {
    221         if (mCallback != null) {
    222             mCallback.onShowDetail(show);
    223         }
    224     }
    225 
    226     private void handleToggleStateChanged(boolean state) {
    227         if (mCallback != null) {
    228             mCallback.onToggleStateChanged(state);
    229         }
    230     }
    231 
    232     private void handleScanStateChanged(boolean state) {
    233         if (mCallback != null) {
    234             mCallback.onScanStateChanged(state);
    235         }
    236     }
    237 
    238     protected void handleUserSwitch(int newUserId) {
    239         handleRefreshState(null);
    240     }
    241 
    242     protected void handleDestroy() {
    243         setListening(false);
    244         mCallback = null;
    245     }
    246 
    247     protected final class H extends Handler {
    248         private static final int SET_CALLBACK = 1;
    249         private static final int CLICK = 2;
    250         private static final int SECONDARY_CLICK = 3;
    251         private static final int LONG_CLICK = 4;
    252         private static final int REFRESH_STATE = 5;
    253         private static final int SHOW_DETAIL = 6;
    254         private static final int USER_SWITCH = 7;
    255         private static final int TOGGLE_STATE_CHANGED = 8;
    256         private static final int SCAN_STATE_CHANGED = 9;
    257         private static final int DESTROY = 10;
    258         private static final int CLEAR_STATE = 11;
    259 
    260         private H(Looper looper) {
    261             super(looper);
    262         }
    263 
    264         @Override
    265         public void handleMessage(Message msg) {
    266             String name = null;
    267             try {
    268                 if (msg.what == SET_CALLBACK) {
    269                     name = "handleSetCallback";
    270                     handleSetCallback((QSTile.Callback)msg.obj);
    271                 } else if (msg.what == CLICK) {
    272                     name = "handleClick";
    273                     mAnnounceNextStateChange = true;
    274                     handleClick();
    275                 } else if (msg.what == SECONDARY_CLICK) {
    276                     name = "handleSecondaryClick";
    277                     handleSecondaryClick();
    278                 } else if (msg.what == LONG_CLICK) {
    279                     name = "handleLongClick";
    280                     handleLongClick();
    281                 } else if (msg.what == REFRESH_STATE) {
    282                     name = "handleRefreshState";
    283                     handleRefreshState(msg.obj);
    284                 } else if (msg.what == SHOW_DETAIL) {
    285                     name = "handleShowDetail";
    286                     handleShowDetail(msg.arg1 != 0);
    287                 } else if (msg.what == USER_SWITCH) {
    288                     name = "handleUserSwitch";
    289                     handleUserSwitch(msg.arg1);
    290                 } else if (msg.what == TOGGLE_STATE_CHANGED) {
    291                     name = "handleToggleStateChanged";
    292                     handleToggleStateChanged(msg.arg1 != 0);
    293                 } else if (msg.what == SCAN_STATE_CHANGED) {
    294                     name = "handleScanStateChanged";
    295                     handleScanStateChanged(msg.arg1 != 0);
    296                 } else if (msg.what == DESTROY) {
    297                     name = "handleDestroy";
    298                     handleDestroy();
    299                 } else if (msg.what == CLEAR_STATE) {
    300                     name = "handleClearState";
    301                     handleClearState();
    302                 } else {
    303                     throw new IllegalArgumentException("Unknown msg: " + msg.what);
    304                 }
    305             } catch (Throwable t) {
    306                 final String error = "Error in " + name;
    307                 Log.w(TAG, error, t);
    308                 mHost.warn(error, t);
    309             }
    310         }
    311     }
    312 
    313     public interface Callback {
    314         void onStateChanged(State state);
    315         void onShowDetail(boolean show);
    316         void onToggleStateChanged(boolean state);
    317         void onScanStateChanged(boolean state);
    318         void onAnnouncementRequested(CharSequence announcement);
    319     }
    320 
    321     public interface Host {
    322         void startActivityDismissingKeyguard(Intent intent);
    323         void warn(String message, Throwable t);
    324         void collapsePanels();
    325         Looper getLooper();
    326         Context getContext();
    327         Collection<QSTile<?>> getTiles();
    328         void setCallback(Callback callback);
    329         BluetoothController getBluetoothController();
    330         LocationController getLocationController();
    331         RotationLockController getRotationLockController();
    332         NetworkController getNetworkController();
    333         ZenModeController getZenModeController();
    334         HotspotController getHotspotController();
    335         CastController getCastController();
    336         FlashlightController getFlashlightController();
    337         KeyguardMonitor getKeyguardMonitor();
    338 
    339         public interface Callback {
    340             void onTilesChanged();
    341         }
    342     }
    343 
    344     public static abstract class Icon {
    345         abstract public Drawable getDrawable(Context context);
    346 
    347         @Override
    348         public int hashCode() {
    349             return Icon.class.hashCode();
    350         }
    351     }
    352 
    353     public static class ResourceIcon extends Icon {
    354         private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
    355 
    356         protected final int mResId;
    357 
    358         private ResourceIcon(int resId) {
    359             mResId = resId;
    360         }
    361 
    362         public static Icon get(int resId) {
    363             Icon icon = ICONS.get(resId);
    364             if (icon == null) {
    365                 icon = new ResourceIcon(resId);
    366                 ICONS.put(resId, icon);
    367             }
    368             return icon;
    369         }
    370 
    371         @Override
    372         public Drawable getDrawable(Context context) {
    373             Drawable d = context.getDrawable(mResId);
    374             if (d instanceof Animatable) {
    375                 ((Animatable) d).start();
    376             }
    377             return d;
    378         }
    379 
    380         @Override
    381         public boolean equals(Object o) {
    382             return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;
    383         }
    384 
    385         @Override
    386         public String toString() {
    387             return String.format("ResourceIcon[resId=0x%08x]", mResId);
    388         }
    389     }
    390 
    391     protected class AnimationIcon extends ResourceIcon {
    392         private boolean mAllowAnimation;
    393 
    394         public AnimationIcon(int resId) {
    395             super(resId);
    396         }
    397 
    398         public void setAllowAnimation(boolean allowAnimation) {
    399             mAllowAnimation = allowAnimation;
    400         }
    401 
    402         @Override
    403         public Drawable getDrawable(Context context) {
    404             // workaround: get a clean state for every new AVD
    405             final AnimatedVectorDrawable d = (AnimatedVectorDrawable) context.getDrawable(mResId)
    406                     .getConstantState().newDrawable();
    407             d.start();
    408             if (mAllowAnimation) {
    409                 mAllowAnimation = false;
    410             } else {
    411                 d.stop(); // skip directly to end state
    412             }
    413             return d;
    414         }
    415     }
    416 
    417     protected enum UserBoolean {
    418         USER_TRUE(true, true),
    419         USER_FALSE(true, false),
    420         BACKGROUND_TRUE(false, true),
    421         BACKGROUND_FALSE(false, false);
    422         public final boolean value;
    423         public final boolean userInitiated;
    424         private UserBoolean(boolean userInitiated, boolean value) {
    425             this.value = value;
    426             this.userInitiated = userInitiated;
    427         }
    428     }
    429 
    430     public static class State {
    431         public boolean visible;
    432         public Icon icon;
    433         public String label;
    434         public String contentDescription;
    435         public String dualLabelContentDescription;
    436         public boolean autoMirrorDrawable = true;
    437 
    438         public boolean copyTo(State other) {
    439             if (other == null) throw new IllegalArgumentException();
    440             if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
    441             final boolean changed = other.visible != visible
    442                     || !Objects.equals(other.icon, icon)
    443                     || !Objects.equals(other.label, label)
    444                     || !Objects.equals(other.contentDescription, contentDescription)
    445                     || !Objects.equals(other.autoMirrorDrawable, autoMirrorDrawable)
    446                     || !Objects.equals(other.dualLabelContentDescription,
    447                     dualLabelContentDescription);
    448             other.visible = visible;
    449             other.icon = icon;
    450             other.label = label;
    451             other.contentDescription = contentDescription;
    452             other.dualLabelContentDescription = dualLabelContentDescription;
    453             other.autoMirrorDrawable = autoMirrorDrawable;
    454             return changed;
    455         }
    456 
    457         @Override
    458         public String toString() {
    459             return toStringBuilder().toString();
    460         }
    461 
    462         protected StringBuilder toStringBuilder() {
    463             final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
    464             sb.append("visible=").append(visible);
    465             sb.append(",icon=").append(icon);
    466             sb.append(",label=").append(label);
    467             sb.append(",contentDescription=").append(contentDescription);
    468             sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
    469             sb.append(",autoMirrorDrawable=").append(autoMirrorDrawable);
    470             return sb.append(']');
    471         }
    472     }
    473 
    474     public static class BooleanState extends State {
    475         public boolean value;
    476 
    477         @Override
    478         public boolean copyTo(State other) {
    479             final BooleanState o = (BooleanState) other;
    480             final boolean changed = super.copyTo(other) || o.value != value;
    481             o.value = value;
    482             return changed;
    483         }
    484 
    485         @Override
    486         protected StringBuilder toStringBuilder() {
    487             final StringBuilder rt = super.toStringBuilder();
    488             rt.insert(rt.length() - 1, ",value=" + value);
    489             return rt;
    490         }
    491     }
    492 
    493     public static final class SignalState extends State {
    494         public boolean enabled;
    495         public boolean connected;
    496         public boolean activityIn;
    497         public boolean activityOut;
    498         public int overlayIconId;
    499         public boolean filter;
    500         public boolean isOverlayIconWide;
    501 
    502         @Override
    503         public boolean copyTo(State other) {
    504             final SignalState o = (SignalState) other;
    505             final boolean changed = o.enabled != enabled
    506                     || o.connected != connected || o.activityIn != activityIn
    507                     || o.activityOut != activityOut
    508                     || o.overlayIconId != overlayIconId
    509                     || o.isOverlayIconWide != isOverlayIconWide;
    510             o.enabled = enabled;
    511             o.connected = connected;
    512             o.activityIn = activityIn;
    513             o.activityOut = activityOut;
    514             o.overlayIconId = overlayIconId;
    515             o.filter = filter;
    516             o.isOverlayIconWide = isOverlayIconWide;
    517             return super.copyTo(other) || changed;
    518         }
    519 
    520         @Override
    521         protected StringBuilder toStringBuilder() {
    522             final StringBuilder rt = super.toStringBuilder();
    523             rt.insert(rt.length() - 1, ",enabled=" + enabled);
    524             rt.insert(rt.length() - 1, ",connected=" + connected);
    525             rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
    526             rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
    527             rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
    528             rt.insert(rt.length() - 1, ",filter=" + filter);
    529             rt.insert(rt.length() - 1, ",wideOverlayIcon=" + isOverlayIconWide);
    530             return rt;
    531         }
    532     }
    533 }
    534