• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.ActivityManager;
20 import android.app.PendingIntent;
21 import android.content.Context;
22 import android.content.Intent;
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.ArraySet;
28 import android.util.Log;
29 import android.util.SparseArray;
30 import android.view.View;
31 import android.view.ViewGroup;
32 
33 import com.android.internal.logging.MetricsLogger;
34 import com.android.internal.logging.MetricsProto.MetricsEvent;
35 import com.android.settingslib.RestrictedLockUtils;
36 import com.android.systemui.qs.QSTile.State;
37 import com.android.systemui.qs.external.TileServices;
38 import com.android.systemui.statusbar.phone.ManagedProfileController;
39 import com.android.systemui.statusbar.policy.BatteryController;
40 import com.android.systemui.statusbar.policy.BluetoothController;
41 import com.android.systemui.statusbar.policy.CastController;
42 import com.android.systemui.statusbar.policy.FlashlightController;
43 import com.android.systemui.statusbar.policy.HotspotController;
44 import com.android.systemui.statusbar.policy.KeyguardMonitor;
45 import com.android.systemui.statusbar.policy.Listenable;
46 import com.android.systemui.statusbar.policy.LocationController;
47 import com.android.systemui.statusbar.policy.NetworkController;
48 import com.android.systemui.statusbar.policy.RotationLockController;
49 import com.android.systemui.statusbar.policy.UserInfoController;
50 import com.android.systemui.statusbar.policy.UserSwitcherController;
51 import com.android.systemui.statusbar.policy.ZenModeController;
52 
53 import java.util.ArrayList;
54 import java.util.Collection;
55 import java.util.Objects;
56 
57 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
58 
59 /**
60  * Base quick-settings tile, extend this to create a new tile.
61  *
62  * State management done on a looper provided by the host.  Tiles should update state in
63  * handleUpdateState.  Callbacks affecting state should use refreshState to trigger another
64  * state update pass on tile looper.
65  */
66 public abstract class QSTile<TState extends State> {
67     protected final String TAG = "Tile." + getClass().getSimpleName();
68     protected static final boolean DEBUG = Log.isLoggable("Tile", Log.DEBUG);
69 
70     protected final Host mHost;
71     protected final Context mContext;
72     protected final H mHandler;
73     protected final Handler mUiHandler = new Handler(Looper.getMainLooper());
74     private final ArraySet<Object> mListeners = new ArraySet<>();
75 
76     private final ArrayList<Callback> mCallbacks = new ArrayList<>();
77     protected TState mState = newTileState();
78     private TState mTmpState = newTileState();
79     private boolean mAnnounceNextStateChange;
80 
81     private String mTileSpec;
82 
newTileState()83     public abstract TState newTileState();
handleClick()84     abstract protected void handleClick();
handleUpdateState(TState state, Object arg)85     abstract protected void handleUpdateState(TState state, Object arg);
86 
87     /**
88      * Declare the category of this tile.
89      *
90      * Categories are defined in {@link com.android.internal.logging.MetricsProto.MetricsEvent}
91      * by editing frameworks/base/proto/src/metrics_constants.proto.
92      */
getMetricsCategory()93     abstract public int getMetricsCategory();
94 
QSTile(Host host)95     protected QSTile(Host host) {
96         mHost = host;
97         mContext = host.getContext();
98         mHandler = new H(host.getLooper());
99     }
100 
101     /**
102      * Adds or removes a listening client for the tile. If the tile has one or more
103      * listening client it will go into the listening state.
104      */
setListening(Object listener, boolean listening)105     public void setListening(Object listener, boolean listening) {
106         if (listening) {
107             if (mListeners.add(listener) && mListeners.size() == 1) {
108                 if (DEBUG) Log.d(TAG, "setListening " + true);
109                 mHandler.obtainMessage(H.SET_LISTENING, 1, 0).sendToTarget();
110             }
111         } else {
112             if (mListeners.remove(listener) && mListeners.size() == 0) {
113                 if (DEBUG) Log.d(TAG, "setListening " + false);
114                 mHandler.obtainMessage(H.SET_LISTENING, 0, 0).sendToTarget();
115             }
116         }
117     }
118 
getTileSpec()119     public String getTileSpec() {
120         return mTileSpec;
121     }
122 
setTileSpec(String tileSpec)123     public void setTileSpec(String tileSpec) {
124         mTileSpec = tileSpec;
125     }
126 
getHost()127     public Host getHost() {
128         return mHost;
129     }
130 
createTileView(Context context)131     public QSIconView createTileView(Context context) {
132         return new QSIconView(context);
133     }
134 
getDetailAdapter()135     public DetailAdapter getDetailAdapter() {
136         return null; // optional
137     }
138 
139     /**
140      * Is a startup check whether this device currently supports this tile.
141      * Should not be used to conditionally hide tiles.  Only checked on tile
142      * creation or whether should be shown in edit screen.
143      */
isAvailable()144     public boolean isAvailable() {
145         return true;
146     }
147 
148     public interface DetailAdapter {
getTitle()149         CharSequence getTitle();
getToggleState()150         Boolean getToggleState();
getToggleEnabled()151         default boolean getToggleEnabled() {
152             return true;
153         }
createDetailView(Context context, View convertView, ViewGroup parent)154         View createDetailView(Context context, View convertView, ViewGroup parent);
getSettingsIntent()155         Intent getSettingsIntent();
setToggleState(boolean state)156         void setToggleState(boolean state);
getMetricsCategory()157         int getMetricsCategory();
158     }
159 
160     // safe to call from any thread
161 
addCallback(Callback callback)162     public void addCallback(Callback callback) {
163         mHandler.obtainMessage(H.ADD_CALLBACK, callback).sendToTarget();
164     }
165 
removeCallback(Callback callback)166     public void removeCallback(Callback callback) {
167         mHandler.obtainMessage(H.REMOVE_CALLBACK, callback).sendToTarget();
168     }
169 
removeCallbacks()170     public void removeCallbacks() {
171         mHandler.sendEmptyMessage(H.REMOVE_CALLBACKS);
172     }
173 
click()174     public void click() {
175         mHandler.sendEmptyMessage(H.CLICK);
176     }
177 
secondaryClick()178     public void secondaryClick() {
179         mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
180     }
181 
longClick()182     public void longClick() {
183         mHandler.sendEmptyMessage(H.LONG_CLICK);
184     }
185 
showDetail(boolean show)186     public void showDetail(boolean show) {
187         mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0).sendToTarget();
188     }
189 
refreshState()190     public final void refreshState() {
191         refreshState(null);
192     }
193 
refreshState(Object arg)194     protected final void refreshState(Object arg) {
195         mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget();
196     }
197 
clearState()198     public final void clearState() {
199         mHandler.sendEmptyMessage(H.CLEAR_STATE);
200     }
201 
userSwitch(int newUserId)202     public void userSwitch(int newUserId) {
203         mHandler.obtainMessage(H.USER_SWITCH, newUserId, 0).sendToTarget();
204     }
205 
fireToggleStateChanged(boolean state)206     public void fireToggleStateChanged(boolean state) {
207         mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
208     }
209 
fireScanStateChanged(boolean state)210     public void fireScanStateChanged(boolean state) {
211         mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
212     }
213 
destroy()214     public void destroy() {
215         mHandler.sendEmptyMessage(H.DESTROY);
216     }
217 
getState()218     public TState getState() {
219         return mState;
220     }
221 
setDetailListening(boolean listening)222     public void setDetailListening(boolean listening) {
223         // optional
224     }
225 
226     // call only on tile worker looper
227 
handleAddCallback(Callback callback)228     private void handleAddCallback(Callback callback) {
229         mCallbacks.add(callback);
230         callback.onStateChanged(mState);
231     }
232 
handleRemoveCallback(Callback callback)233     private void handleRemoveCallback(Callback callback) {
234         mCallbacks.remove(callback);
235     }
236 
handleRemoveCallbacks()237     private void handleRemoveCallbacks() {
238         mCallbacks.clear();
239     }
240 
handleSecondaryClick()241     protected void handleSecondaryClick() {
242         // Default to normal click.
243         handleClick();
244     }
245 
handleLongClick()246     protected void handleLongClick() {
247         MetricsLogger.action(mContext, MetricsEvent.ACTION_QS_LONG_PRESS, getTileSpec());
248         mHost.startActivityDismissingKeyguard(getLongClickIntent());
249     }
250 
getLongClickIntent()251     public abstract Intent getLongClickIntent();
252 
handleClearState()253     protected void handleClearState() {
254         mTmpState = newTileState();
255         mState = newTileState();
256     }
257 
handleRefreshState(Object arg)258     protected void handleRefreshState(Object arg) {
259         handleUpdateState(mTmpState, arg);
260         final boolean changed = mTmpState.copyTo(mState);
261         if (changed) {
262             handleStateChanged();
263         }
264     }
265 
handleStateChanged()266     private void handleStateChanged() {
267         boolean delayAnnouncement = shouldAnnouncementBeDelayed();
268         if (mCallbacks.size() != 0) {
269             for (int i = 0; i < mCallbacks.size(); i++) {
270                 mCallbacks.get(i).onStateChanged(mState);
271             }
272             if (mAnnounceNextStateChange && !delayAnnouncement) {
273                 String announcement = composeChangeAnnouncement();
274                 if (announcement != null) {
275                     mCallbacks.get(0).onAnnouncementRequested(announcement);
276                 }
277             }
278         }
279         mAnnounceNextStateChange = mAnnounceNextStateChange && delayAnnouncement;
280     }
281 
shouldAnnouncementBeDelayed()282     protected boolean shouldAnnouncementBeDelayed() {
283         return false;
284     }
285 
composeChangeAnnouncement()286     protected String composeChangeAnnouncement() {
287         return null;
288     }
289 
handleShowDetail(boolean show)290     private void handleShowDetail(boolean show) {
291         for (int i = 0; i < mCallbacks.size(); i++) {
292             mCallbacks.get(i).onShowDetail(show);
293         }
294     }
295 
handleToggleStateChanged(boolean state)296     private void handleToggleStateChanged(boolean state) {
297         for (int i = 0; i < mCallbacks.size(); i++) {
298             mCallbacks.get(i).onToggleStateChanged(state);
299         }
300     }
301 
handleScanStateChanged(boolean state)302     private void handleScanStateChanged(boolean state) {
303         for (int i = 0; i < mCallbacks.size(); i++) {
304             mCallbacks.get(i).onScanStateChanged(state);
305         }
306     }
307 
handleUserSwitch(int newUserId)308     protected void handleUserSwitch(int newUserId) {
309         handleRefreshState(null);
310     }
311 
setListening(boolean listening)312     protected abstract void setListening(boolean listening);
313 
handleDestroy()314     protected void handleDestroy() {
315         setListening(false);
316         mCallbacks.clear();
317     }
318 
checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction)319     protected void checkIfRestrictionEnforcedByAdminOnly(State state, String userRestriction) {
320         EnforcedAdmin admin = RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
321                 userRestriction, ActivityManager.getCurrentUser());
322         if (admin != null && !RestrictedLockUtils.hasBaseUserRestriction(mContext,
323                 userRestriction, ActivityManager.getCurrentUser())) {
324             state.disabledByPolicy = true;
325             state.enforcedAdmin = admin;
326         } else {
327             state.disabledByPolicy = false;
328             state.enforcedAdmin = null;
329         }
330     }
331 
getTileLabel()332     public abstract CharSequence getTileLabel();
333 
334     protected final class H extends Handler {
335         private static final int ADD_CALLBACK = 1;
336         private static final int CLICK = 2;
337         private static final int SECONDARY_CLICK = 3;
338         private static final int LONG_CLICK = 4;
339         private static final int REFRESH_STATE = 5;
340         private static final int SHOW_DETAIL = 6;
341         private static final int USER_SWITCH = 7;
342         private static final int TOGGLE_STATE_CHANGED = 8;
343         private static final int SCAN_STATE_CHANGED = 9;
344         private static final int DESTROY = 10;
345         private static final int CLEAR_STATE = 11;
346         private static final int REMOVE_CALLBACKS = 12;
347         private static final int REMOVE_CALLBACK = 13;
348         private static final int SET_LISTENING = 14;
349 
H(Looper looper)350         private H(Looper looper) {
351             super(looper);
352         }
353 
354         @Override
handleMessage(Message msg)355         public void handleMessage(Message msg) {
356             String name = null;
357             try {
358                 if (msg.what == ADD_CALLBACK) {
359                     name = "handleAddCallback";
360                     handleAddCallback((QSTile.Callback) msg.obj);
361                 } else if (msg.what == REMOVE_CALLBACKS) {
362                     name = "handleRemoveCallbacks";
363                     handleRemoveCallbacks();
364                 } else if (msg.what == REMOVE_CALLBACK) {
365                     name = "handleRemoveCallback";
366                     handleRemoveCallback((QSTile.Callback) msg.obj);
367                 } else if (msg.what == CLICK) {
368                     name = "handleClick";
369                     if (mState.disabledByPolicy) {
370                         Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
371                                 mContext, mState.enforcedAdmin);
372                         mHost.startActivityDismissingKeyguard(intent);
373                     } else {
374                         mAnnounceNextStateChange = true;
375                         handleClick();
376                     }
377                 } else if (msg.what == SECONDARY_CLICK) {
378                     name = "handleSecondaryClick";
379                     handleSecondaryClick();
380                 } else if (msg.what == LONG_CLICK) {
381                     name = "handleLongClick";
382                     handleLongClick();
383                 } else if (msg.what == REFRESH_STATE) {
384                     name = "handleRefreshState";
385                     handleRefreshState(msg.obj);
386                 } else if (msg.what == SHOW_DETAIL) {
387                     name = "handleShowDetail";
388                     handleShowDetail(msg.arg1 != 0);
389                 } else if (msg.what == USER_SWITCH) {
390                     name = "handleUserSwitch";
391                     handleUserSwitch(msg.arg1);
392                 } else if (msg.what == TOGGLE_STATE_CHANGED) {
393                     name = "handleToggleStateChanged";
394                     handleToggleStateChanged(msg.arg1 != 0);
395                 } else if (msg.what == SCAN_STATE_CHANGED) {
396                     name = "handleScanStateChanged";
397                     handleScanStateChanged(msg.arg1 != 0);
398                 } else if (msg.what == DESTROY) {
399                     name = "handleDestroy";
400                     handleDestroy();
401                 } else if (msg.what == CLEAR_STATE) {
402                     name = "handleClearState";
403                     handleClearState();
404                 } else if (msg.what == SET_LISTENING) {
405                     name = "setListening";
406                     setListening(msg.arg1 != 0);
407                 } else {
408                     throw new IllegalArgumentException("Unknown msg: " + msg.what);
409                 }
410             } catch (Throwable t) {
411                 final String error = "Error in " + name;
412                 Log.w(TAG, error, t);
413                 mHost.warn(error, t);
414             }
415         }
416     }
417 
418     public interface Callback {
onStateChanged(State state)419         void onStateChanged(State state);
onShowDetail(boolean show)420         void onShowDetail(boolean show);
onToggleStateChanged(boolean state)421         void onToggleStateChanged(boolean state);
onScanStateChanged(boolean state)422         void onScanStateChanged(boolean state);
onAnnouncementRequested(CharSequence announcement)423         void onAnnouncementRequested(CharSequence announcement);
424     }
425 
426     public interface Host {
startActivityDismissingKeyguard(Intent intent)427         void startActivityDismissingKeyguard(Intent intent);
startActivityDismissingKeyguard(PendingIntent intent)428         void startActivityDismissingKeyguard(PendingIntent intent);
startRunnableDismissingKeyguard(Runnable runnable)429         void startRunnableDismissingKeyguard(Runnable runnable);
warn(String message, Throwable t)430         void warn(String message, Throwable t);
collapsePanels()431         void collapsePanels();
animateToggleQSExpansion()432         void animateToggleQSExpansion();
openPanels()433         void openPanels();
getLooper()434         Looper getLooper();
getContext()435         Context getContext();
getTiles()436         Collection<QSTile<?>> getTiles();
addCallback(Callback callback)437         void addCallback(Callback callback);
removeCallback(Callback callback)438         void removeCallback(Callback callback);
getBluetoothController()439         BluetoothController getBluetoothController();
getLocationController()440         LocationController getLocationController();
getRotationLockController()441         RotationLockController getRotationLockController();
getNetworkController()442         NetworkController getNetworkController();
getZenModeController()443         ZenModeController getZenModeController();
getHotspotController()444         HotspotController getHotspotController();
getCastController()445         CastController getCastController();
getFlashlightController()446         FlashlightController getFlashlightController();
getKeyguardMonitor()447         KeyguardMonitor getKeyguardMonitor();
getUserSwitcherController()448         UserSwitcherController getUserSwitcherController();
getUserInfoController()449         UserInfoController getUserInfoController();
getBatteryController()450         BatteryController getBatteryController();
getTileServices()451         TileServices getTileServices();
removeTile(String tileSpec)452         void removeTile(String tileSpec);
getManagedProfileController()453         ManagedProfileController getManagedProfileController();
454 
455 
456         public interface Callback {
onTilesChanged()457             void onTilesChanged();
458         }
459     }
460 
461     public static abstract class Icon {
getDrawable(Context context)462         abstract public Drawable getDrawable(Context context);
463 
getInvisibleDrawable(Context context)464         public Drawable getInvisibleDrawable(Context context) {
465             return getDrawable(context);
466         }
467 
468         @Override
hashCode()469         public int hashCode() {
470             return Icon.class.hashCode();
471         }
472 
getPadding()473         public int getPadding() {
474             return 0;
475         }
476     }
477 
478     public static class DrawableIcon extends Icon {
479         protected final Drawable mDrawable;
480 
DrawableIcon(Drawable drawable)481         public DrawableIcon(Drawable drawable) {
482             mDrawable = drawable;
483         }
484 
485         @Override
getDrawable(Context context)486         public Drawable getDrawable(Context context) {
487             return mDrawable;
488         }
489 
490         @Override
getInvisibleDrawable(Context context)491         public Drawable getInvisibleDrawable(Context context) {
492             return mDrawable;
493         }
494     }
495 
496     public static class ResourceIcon extends Icon {
497         private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
498 
499         protected final int mResId;
500 
ResourceIcon(int resId)501         private ResourceIcon(int resId) {
502             mResId = resId;
503         }
504 
get(int resId)505         public static Icon get(int resId) {
506             Icon icon = ICONS.get(resId);
507             if (icon == null) {
508                 icon = new ResourceIcon(resId);
509                 ICONS.put(resId, icon);
510             }
511             return icon;
512         }
513 
514         @Override
getDrawable(Context context)515         public Drawable getDrawable(Context context) {
516             return context.getDrawable(mResId);
517         }
518 
519         @Override
getInvisibleDrawable(Context context)520         public Drawable getInvisibleDrawable(Context context) {
521             return context.getDrawable(mResId);
522         }
523 
524         @Override
equals(Object o)525         public boolean equals(Object o) {
526             return o instanceof ResourceIcon && ((ResourceIcon) o).mResId == mResId;
527         }
528 
529         @Override
toString()530         public String toString() {
531             return String.format("ResourceIcon[resId=0x%08x]", mResId);
532         }
533     }
534 
535     protected class AnimationIcon extends ResourceIcon {
536         private final int mAnimatedResId;
537 
AnimationIcon(int resId, int staticResId)538         public AnimationIcon(int resId, int staticResId) {
539             super(staticResId);
540             mAnimatedResId = resId;
541         }
542 
543         @Override
getDrawable(Context context)544         public Drawable getDrawable(Context context) {
545             // workaround: get a clean state for every new AVD
546             return context.getDrawable(mAnimatedResId).getConstantState().newDrawable();
547         }
548     }
549 
550     public static class State {
551         public Icon icon;
552         public CharSequence label;
553         public CharSequence contentDescription;
554         public CharSequence dualLabelContentDescription;
555         public CharSequence minimalContentDescription;
556         public boolean autoMirrorDrawable = true;
557         public boolean disabledByPolicy;
558         public EnforcedAdmin enforcedAdmin;
559         public String minimalAccessibilityClassName;
560         public String expandedAccessibilityClassName;
561 
copyTo(State other)562         public boolean copyTo(State other) {
563             if (other == null) throw new IllegalArgumentException();
564             if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
565             final boolean changed = !Objects.equals(other.icon, icon)
566                     || !Objects.equals(other.label, label)
567                     || !Objects.equals(other.contentDescription, contentDescription)
568                     || !Objects.equals(other.autoMirrorDrawable, autoMirrorDrawable)
569                     || !Objects.equals(other.dualLabelContentDescription,
570                     dualLabelContentDescription)
571                     || !Objects.equals(other.minimalContentDescription,
572                     minimalContentDescription)
573                     || !Objects.equals(other.minimalAccessibilityClassName,
574                     minimalAccessibilityClassName)
575                     || !Objects.equals(other.expandedAccessibilityClassName,
576                     expandedAccessibilityClassName)
577                     || !Objects.equals(other.disabledByPolicy, disabledByPolicy)
578                     || !Objects.equals(other.enforcedAdmin, enforcedAdmin);
579             other.icon = icon;
580             other.label = label;
581             other.contentDescription = contentDescription;
582             other.dualLabelContentDescription = dualLabelContentDescription;
583             other.minimalContentDescription = minimalContentDescription;
584             other.minimalAccessibilityClassName = minimalAccessibilityClassName;
585             other.expandedAccessibilityClassName = expandedAccessibilityClassName;
586             other.autoMirrorDrawable = autoMirrorDrawable;
587             other.disabledByPolicy = disabledByPolicy;
588             if (enforcedAdmin == null) {
589                 other.enforcedAdmin = null;
590             } else if (other.enforcedAdmin == null) {
591                 other.enforcedAdmin = new EnforcedAdmin(enforcedAdmin);
592             } else {
593                 enforcedAdmin.copyTo(other.enforcedAdmin);
594             }
595             return changed;
596         }
597 
598         @Override
toString()599         public String toString() {
600             return toStringBuilder().toString();
601         }
602 
toStringBuilder()603         protected StringBuilder toStringBuilder() {
604             final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
605             sb.append(",icon=").append(icon);
606             sb.append(",label=").append(label);
607             sb.append(",contentDescription=").append(contentDescription);
608             sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription);
609             sb.append(",minimalContentDescription=").append(minimalContentDescription);
610             sb.append(",minimalAccessibilityClassName=").append(minimalAccessibilityClassName);
611             sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName);
612             sb.append(",autoMirrorDrawable=").append(autoMirrorDrawable);
613             sb.append(",disabledByPolicy=").append(disabledByPolicy);
614             sb.append(",enforcedAdmin=").append(enforcedAdmin);
615             return sb.append(']');
616         }
617     }
618 
619     public static class BooleanState extends State {
620         public boolean value;
621 
622         @Override
copyTo(State other)623         public boolean copyTo(State other) {
624             final BooleanState o = (BooleanState) other;
625             final boolean changed = super.copyTo(other) || o.value != value;
626             o.value = value;
627             return changed;
628         }
629 
630         @Override
toStringBuilder()631         protected StringBuilder toStringBuilder() {
632             final StringBuilder rt = super.toStringBuilder();
633             rt.insert(rt.length() - 1, ",value=" + value);
634             return rt;
635         }
636     }
637 
638     public static class AirplaneBooleanState extends BooleanState {
639         public boolean isAirplaneMode;
640 
641         @Override
copyTo(State other)642         public boolean copyTo(State other) {
643             final AirplaneBooleanState o = (AirplaneBooleanState) other;
644             final boolean changed = super.copyTo(other) || o.isAirplaneMode != isAirplaneMode;
645             o.isAirplaneMode = isAirplaneMode;
646             return changed;
647         }
648     }
649 
650     public static final class SignalState extends BooleanState {
651         public boolean connected;
652         public boolean activityIn;
653         public boolean activityOut;
654         public int overlayIconId;
655         public boolean filter;
656         public boolean isOverlayIconWide;
657 
658         @Override
copyTo(State other)659         public boolean copyTo(State other) {
660             final SignalState o = (SignalState) other;
661             final boolean changed = o.connected != connected || o.activityIn != activityIn
662                     || o.activityOut != activityOut
663                     || o.overlayIconId != overlayIconId
664                     || o.isOverlayIconWide != isOverlayIconWide;
665             o.connected = connected;
666             o.activityIn = activityIn;
667             o.activityOut = activityOut;
668             o.overlayIconId = overlayIconId;
669             o.filter = filter;
670             o.isOverlayIconWide = isOverlayIconWide;
671             return super.copyTo(other) || changed;
672         }
673 
674         @Override
toStringBuilder()675         protected StringBuilder toStringBuilder() {
676             final StringBuilder rt = super.toStringBuilder();
677             rt.insert(rt.length() - 1, ",connected=" + connected);
678             rt.insert(rt.length() - 1, ",activityIn=" + activityIn);
679             rt.insert(rt.length() - 1, ",activityOut=" + activityOut);
680             rt.insert(rt.length() - 1, ",overlayIconId=" + overlayIconId);
681             rt.insert(rt.length() - 1, ",filter=" + filter);
682             rt.insert(rt.length() - 1, ",wideOverlayIcon=" + isOverlayIconWide);
683             return rt;
684         }
685     }
686 }
687