1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui.plugins.qs; 16 17 import android.annotation.NonNull; 18 import android.content.Context; 19 import android.content.res.Resources; 20 import android.graphics.drawable.Drawable; 21 import android.metrics.LogMaker; 22 import android.service.quicksettings.Tile; 23 import android.text.TextUtils; 24 import android.view.View; 25 26 import androidx.annotation.Nullable; 27 28 import com.android.internal.logging.InstanceId; 29 import com.android.systemui.plugins.annotations.DependsOn; 30 import com.android.systemui.plugins.annotations.ProvidesInterface; 31 import com.android.systemui.plugins.qs.QSTile.Callback; 32 import com.android.systemui.plugins.qs.QSTile.Icon; 33 import com.android.systemui.plugins.qs.QSTile.State; 34 35 import java.util.Objects; 36 import java.util.function.Supplier; 37 38 @ProvidesInterface(version = QSTile.VERSION) 39 @DependsOn(target = QSIconView.class) 40 @DependsOn(target = Callback.class) 41 @DependsOn(target = Icon.class) 42 @DependsOn(target = State.class) 43 public interface QSTile { 44 int VERSION = 4; 45 getTileSpec()46 String getTileSpec(); 47 isAvailable()48 boolean isAvailable(); setTileSpec(String tileSpec)49 void setTileSpec(String tileSpec); 50 clearState()51 @Deprecated default void clearState() {} refreshState()52 void refreshState(); 53 addCallback(Callback callback)54 void addCallback(Callback callback); removeCallback(Callback callback)55 void removeCallback(Callback callback); removeCallbacks()56 void removeCallbacks(); 57 createTileView(Context context)58 QSIconView createTileView(Context context); 59 60 /** 61 * The tile was clicked. 62 * 63 * @param view The view that was clicked. 64 */ click(@ullable View view)65 void click(@Nullable View view); 66 67 /** 68 * The tile secondary click was triggered. 69 * 70 * @param view The view that was clicked. 71 */ secondaryClick(@ullable View view)72 void secondaryClick(@Nullable View view); 73 74 /** 75 * The tile was long clicked. 76 * 77 * @param view The view that was clicked. 78 */ longClick(@ullable View view)79 void longClick(@Nullable View view); 80 userSwitch(int currentUser)81 void userSwitch(int currentUser); 82 83 /** 84 * @deprecated not needed as {@link com.android.internal.logging.UiEvent} will use 85 * {@link #getMetricsSpec} 86 */ 87 @Deprecated getMetricsCategory()88 int getMetricsCategory(); 89 setListening(Object client, boolean listening)90 void setListening(Object client, boolean listening); setDetailListening(boolean show)91 void setDetailListening(boolean show); 92 destroy()93 void destroy(); 94 getTileLabel()95 CharSequence getTileLabel(); 96 getState()97 State getState(); 98 populate(LogMaker logMaker)99 default LogMaker populate(LogMaker logMaker) { 100 return logMaker; 101 } 102 103 /** 104 * Return a string to be used to identify the tile in UiEvents. 105 */ getMetricsSpec()106 default String getMetricsSpec() { 107 return getClass().getSimpleName(); 108 } 109 110 /** 111 * Return an {@link InstanceId} to be used to identify the tile in UiEvents. 112 */ getInstanceId()113 InstanceId getInstanceId(); 114 isTileReady()115 default boolean isTileReady() { 116 return false; 117 } 118 119 /** 120 * Return whether the tile is set to its listening state and therefore receiving updates and 121 * refreshes from controllers 122 */ isListening()123 boolean isListening(); 124 125 @ProvidesInterface(version = Callback.VERSION) 126 interface Callback { 127 static final int VERSION = 2; onStateChanged(State state)128 void onStateChanged(State state); 129 } 130 131 @ProvidesInterface(version = Icon.VERSION) 132 public static abstract class Icon { 133 public static final int VERSION = 1; getDrawable(Context context)134 abstract public Drawable getDrawable(Context context); 135 getInvisibleDrawable(Context context)136 public Drawable getInvisibleDrawable(Context context) { 137 return getDrawable(context); 138 } 139 140 @Override hashCode()141 public int hashCode() { 142 return Icon.class.hashCode(); 143 } 144 getPadding()145 public int getPadding() { 146 return 0; 147 } 148 149 @Override 150 @NonNull toString()151 public String toString() { 152 return "Icon"; 153 } 154 } 155 156 @ProvidesInterface(version = State.VERSION) 157 public static class State { 158 public static final int VERSION = 1; 159 public static final int DEFAULT_STATE = Tile.STATE_ACTIVE; 160 161 public Icon icon; 162 public Supplier<Icon> iconSupplier; 163 public int state = DEFAULT_STATE; 164 public CharSequence label; 165 @Nullable public CharSequence secondaryLabel; 166 public CharSequence contentDescription; 167 @Nullable public CharSequence stateDescription; 168 public CharSequence dualLabelContentDescription; 169 public boolean disabledByPolicy; 170 public boolean dualTarget = false; 171 public boolean isTransient = false; 172 public String expandedAccessibilityClassName; 173 public SlashState slash; 174 public boolean handlesLongClick = true; 175 public boolean showRippleEffect = true; 176 @Nullable 177 public Drawable sideViewCustomDrawable; 178 public String spec; 179 180 /** Get the state text. */ getStateText(int arrayResId, Resources resources)181 public String getStateText(int arrayResId, Resources resources) { 182 if (state == Tile.STATE_UNAVAILABLE || this instanceof QSTile.BooleanState) { 183 String[] array = resources.getStringArray(arrayResId); 184 return array[state]; 185 } else { 186 return ""; 187 } 188 } 189 190 /** Get the text for secondaryLabel. */ getSecondaryLabel(String stateText)191 public String getSecondaryLabel(String stateText) { 192 // Use a local reference as the value might change from other threads 193 CharSequence localSecondaryLabel = secondaryLabel; 194 if (TextUtils.isEmpty(localSecondaryLabel)) { 195 return stateText; 196 } 197 return localSecondaryLabel.toString(); 198 } 199 copyTo(State other)200 public boolean copyTo(State other) { 201 if (other == null) throw new IllegalArgumentException(); 202 if (!other.getClass().equals(getClass())) throw new IllegalArgumentException(); 203 final boolean changed = !Objects.equals(other.spec, spec) 204 || !Objects.equals(other.icon, icon) 205 || !Objects.equals(other.iconSupplier, iconSupplier) 206 || !Objects.equals(other.label, label) 207 || !Objects.equals(other.secondaryLabel, secondaryLabel) 208 || !Objects.equals(other.contentDescription, contentDescription) 209 || !Objects.equals(other.stateDescription, stateDescription) 210 || !Objects.equals(other.dualLabelContentDescription, 211 dualLabelContentDescription) 212 || !Objects.equals(other.expandedAccessibilityClassName, 213 expandedAccessibilityClassName) 214 || !Objects.equals(other.disabledByPolicy, disabledByPolicy) 215 || !Objects.equals(other.state, state) 216 || !Objects.equals(other.isTransient, isTransient) 217 || !Objects.equals(other.dualTarget, dualTarget) 218 || !Objects.equals(other.slash, slash) 219 || !Objects.equals(other.handlesLongClick, handlesLongClick) 220 || !Objects.equals(other.showRippleEffect, showRippleEffect) 221 || !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable); 222 other.spec = spec; 223 other.icon = icon; 224 other.iconSupplier = iconSupplier; 225 other.label = label; 226 other.secondaryLabel = secondaryLabel; 227 other.contentDescription = contentDescription; 228 other.stateDescription = stateDescription; 229 other.dualLabelContentDescription = dualLabelContentDescription; 230 other.expandedAccessibilityClassName = expandedAccessibilityClassName; 231 other.disabledByPolicy = disabledByPolicy; 232 other.state = state; 233 other.dualTarget = dualTarget; 234 other.isTransient = isTransient; 235 other.slash = slash != null ? slash.copy() : null; 236 other.handlesLongClick = handlesLongClick; 237 other.showRippleEffect = showRippleEffect; 238 other.sideViewCustomDrawable = sideViewCustomDrawable; 239 return changed; 240 } 241 242 @Override toString()243 public String toString() { 244 return toStringBuilder().toString(); 245 } 246 247 // Used in dumps to determine current state of a tile. 248 // This string may be used for CTS testing of tiles, so removing elements is discouraged. toStringBuilder()249 protected StringBuilder toStringBuilder() { 250 final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('['); 251 sb.append("spec=").append(spec); 252 sb.append(",icon=").append(icon); 253 sb.append(",iconSupplier=").append(iconSupplier); 254 sb.append(",label=").append(label); 255 sb.append(",secondaryLabel=").append(secondaryLabel); 256 sb.append(",contentDescription=").append(contentDescription); 257 sb.append(",stateDescription=").append(stateDescription); 258 sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription); 259 sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName); 260 sb.append(",disabledByPolicy=").append(disabledByPolicy); 261 sb.append(",dualTarget=").append(dualTarget); 262 sb.append(",isTransient=").append(isTransient); 263 sb.append(",state=").append(state); 264 sb.append(",slash=\"").append(slash).append("\""); 265 sb.append(",sideViewCustomDrawable=").append(sideViewCustomDrawable); 266 return sb.append(']'); 267 } 268 copy()269 public State copy() { 270 State state = new State(); 271 copyTo(state); 272 return state; 273 } 274 } 275 276 @ProvidesInterface(version = BooleanState.VERSION) 277 public static class BooleanState extends State { 278 public static final int VERSION = 1; 279 public boolean value; 280 public boolean forceExpandIcon; 281 282 @Override copyTo(State other)283 public boolean copyTo(State other) { 284 final BooleanState o = (BooleanState) other; 285 final boolean changed = super.copyTo(other) 286 || o.value != value 287 || o.forceExpandIcon != forceExpandIcon; 288 o.value = value; 289 o.forceExpandIcon = forceExpandIcon; 290 return changed; 291 } 292 293 @Override toStringBuilder()294 protected StringBuilder toStringBuilder() { 295 final StringBuilder rt = super.toStringBuilder(); 296 rt.insert(rt.length() - 1, ",value=" + value); 297 rt.insert(rt.length() - 1, ",forceExpandIcon=" + forceExpandIcon); 298 return rt; 299 } 300 301 @Override copy()302 public State copy() { 303 BooleanState state = new BooleanState(); 304 copyTo(state); 305 return state; 306 } 307 } 308 309 @ProvidesInterface(version = SignalState.VERSION) 310 public static final class SignalState extends BooleanState { 311 public static final int VERSION = 1; 312 public boolean activityIn; 313 public boolean activityOut; 314 public boolean isOverlayIconWide; 315 public int overlayIconId; 316 317 @Override copyTo(State other)318 public boolean copyTo(State other) { 319 final SignalState o = (SignalState) other; 320 final boolean changed = o.activityIn != activityIn 321 || o.activityOut != activityOut 322 || o.isOverlayIconWide != isOverlayIconWide 323 || o.overlayIconId != overlayIconId; 324 o.activityIn = activityIn; 325 o.activityOut = activityOut; 326 o.isOverlayIconWide = isOverlayIconWide; 327 o.overlayIconId = overlayIconId; 328 return super.copyTo(other) || changed; 329 } 330 331 @Override toStringBuilder()332 protected StringBuilder toStringBuilder() { 333 final StringBuilder rt = super.toStringBuilder(); 334 rt.insert(rt.length() - 1, ",activityIn=" + activityIn); 335 rt.insert(rt.length() - 1, ",activityOut=" + activityOut); 336 return rt; 337 } 338 339 @Override copy()340 public State copy() { 341 SignalState state = new SignalState(); 342 copyTo(state); 343 return state; 344 } 345 } 346 347 @ProvidesInterface(version = SlashState.VERSION) 348 public static class SlashState { 349 public static final int VERSION = 2; 350 351 public boolean isSlashed; 352 public float rotation; 353 354 @Override toString()355 public String toString() { 356 return "isSlashed=" + isSlashed + ",rotation=" + rotation; 357 } 358 359 @Override equals(Object o)360 public boolean equals(Object o) { 361 if (o == null) return false; 362 try { 363 return (((SlashState) o).rotation == rotation) 364 && (((SlashState) o).isSlashed == isSlashed); 365 } catch (ClassCastException e) { 366 return false; 367 } 368 } 369 copy()370 public SlashState copy() { 371 SlashState state = new SlashState(); 372 state.rotation = rotation; 373 state.isSlashed = isSlashed; 374 return state; 375 } 376 } 377 } 378