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