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.content.Context; 18 import android.content.res.Resources; 19 import android.graphics.drawable.Drawable; 20 import android.metrics.LogMaker; 21 import android.service.quicksettings.Tile; 22 import android.text.TextUtils; 23 24 import androidx.annotation.NonNull; 25 import androidx.annotation.Nullable; 26 27 import com.android.internal.logging.InstanceId; 28 import com.android.systemui.animation.Expandable; 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.Consumer; 37 import java.util.function.Supplier; 38 39 @ProvidesInterface(version = QSTile.VERSION) 40 @DependsOn(target = QSIconView.class) 41 @DependsOn(target = Callback.class) 42 @DependsOn(target = Icon.class) 43 @DependsOn(target = State.class) 44 public interface QSTile { 45 int VERSION = 5; 46 getTileSpec()47 String getTileSpec(); 48 isAvailable()49 boolean isAvailable(); setTileSpec(String tileSpec)50 void setTileSpec(String tileSpec); 51 clearState()52 @Deprecated default void clearState() {} refreshState()53 void refreshState(); 54 addCallback(Callback callback)55 void addCallback(Callback callback); removeCallback(Callback callback)56 void removeCallback(Callback callback); removeCallbacks()57 void removeCallbacks(); 58 59 /** 60 * The tile was clicked. 61 * 62 * @param expandable {@link Expandable} that was clicked. 63 */ click(@ullable Expandable expandable)64 void click(@Nullable Expandable expandable); 65 66 /** 67 * The tile secondary click was triggered. 68 * 69 * @param expandable {@link Expandable} that was clicked. 70 */ secondaryClick(@ullable Expandable expandable)71 void secondaryClick(@Nullable Expandable expandable); 72 73 /** 74 * The tile was long clicked. 75 * 76 * @param expandable {@link Expandable} that was clicked. 77 */ longClick(@ullable Expandable expandable)78 void longClick(@Nullable Expandable expandable); 79 userSwitch(int currentUser)80 void userSwitch(int currentUser); getCurrentTileUser()81 int getCurrentTileUser(); 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 97 @NonNull getState()98 State getState(); 99 populate(LogMaker logMaker)100 default LogMaker populate(LogMaker logMaker) { 101 return logMaker; 102 } 103 104 /** 105 * Return a string to be used to identify the tile in UiEvents. 106 */ getMetricsSpec()107 default String getMetricsSpec() { 108 return getClass().getSimpleName(); 109 } 110 111 /** 112 * Return an {@link InstanceId} to be used to identify the tile in UiEvents. 113 */ getInstanceId()114 InstanceId getInstanceId(); 115 isTileReady()116 default boolean isTileReady() { 117 return false; 118 } 119 120 /** 121 * Return whether the tile is set to its listening state and therefore receiving updates and 122 * refreshes from controllers 123 */ isListening()124 boolean isListening(); 125 126 /** 127 * Get this tile's {@link TileDetailsViewModel} through a callback. 128 * 129 * Please only override this method if the tile can't get its {@link TileDetailsViewModel} 130 * synchronously and thus need a callback to defer it. 131 * 132 * @return a boolean indicating whether this tile has a {@link TileDetailsViewModel}. The tile's 133 * {@link TileDetailsViewModel} will be passed to the callback. Please always return true when 134 * overriding this method. Return false will make the tile display its dialog instead of details 135 * view, and it will not wait for the callback to be returned before proceeding to show the 136 * dialog. 137 */ getDetailsViewModel(Consumer<TileDetailsViewModel> callback)138 default boolean getDetailsViewModel(Consumer<TileDetailsViewModel> callback) { 139 TileDetailsViewModel tileDetailsViewModel = getDetailsViewModel(); 140 callback.accept(tileDetailsViewModel); 141 return tileDetailsViewModel != null; 142 } 143 144 /** 145 * Return this tile's {@link TileDetailsViewModel} to be used to render the TileDetailsView. 146 * 147 * Please only override this method if the tile doesn't need a callback to set its 148 * {@link TileDetailsViewModel}. 149 */ getDetailsViewModel()150 default TileDetailsViewModel getDetailsViewModel() { 151 return null; 152 } 153 isDestroyed()154 boolean isDestroyed(); 155 156 @ProvidesInterface(version = Callback.VERSION) 157 interface Callback { 158 static final int VERSION = 2; onStateChanged(State state)159 void onStateChanged(State state); 160 } 161 162 @ProvidesInterface(version = Icon.VERSION) 163 public static abstract class Icon { 164 public static final int VERSION = 1; getDrawable(Context context)165 abstract public Drawable getDrawable(Context context); 166 getInvisibleDrawable(Context context)167 public Drawable getInvisibleDrawable(Context context) { 168 return getDrawable(context); 169 } 170 171 @Override hashCode()172 public int hashCode() { 173 return Icon.class.hashCode(); 174 } 175 getPadding()176 public int getPadding() { 177 return 0; 178 } 179 180 @Override 181 @NonNull toString()182 public String toString() { 183 return "Icon"; 184 } 185 } 186 187 @ProvidesInterface(version = State.VERSION) 188 public static class State { 189 public static final int VERSION = 1; 190 public static final int DEFAULT_STATE = Tile.STATE_ACTIVE; 191 192 public Icon icon; 193 public Supplier<Icon> iconSupplier; 194 public int state = DEFAULT_STATE; 195 public CharSequence label; 196 @Nullable public CharSequence secondaryLabel; 197 public CharSequence contentDescription; 198 @Nullable public CharSequence stateDescription; 199 public CharSequence dualLabelContentDescription; 200 public boolean disabledByPolicy; 201 public boolean dualTarget = false; 202 public boolean isTransient = false; 203 public String expandedAccessibilityClassName; 204 public boolean handlesLongClick = true; 205 public boolean handlesSecondaryClick = false; 206 @Nullable 207 public Drawable sideViewCustomDrawable; 208 public String spec; 209 210 /** Get the state text. */ getStateText(int arrayResId, Resources resources)211 public CharSequence getStateText(int arrayResId, Resources resources) { 212 if (state == Tile.STATE_UNAVAILABLE || this instanceof QSTile.BooleanState) { 213 String[] array = resources.getStringArray(arrayResId); 214 return array[state]; 215 } else { 216 return ""; 217 } 218 } 219 220 /** 221 * If the current secondaryLabel value is not empty, ignore the given input and return 222 * the current value. Otherwise return current value. 223 */ getSecondaryLabel(CharSequence stateText)224 public CharSequence getSecondaryLabel(CharSequence stateText) { 225 // Use a local reference as the value might change from other threads 226 CharSequence localSecondaryLabel = secondaryLabel; 227 if (TextUtils.isEmpty(localSecondaryLabel)) { 228 return stateText; 229 } 230 return localSecondaryLabel; 231 } 232 copyTo(State other)233 public boolean copyTo(State other) { 234 if (other == null) throw new IllegalArgumentException(); 235 if (!other.getClass().equals(getClass())) throw new IllegalArgumentException(); 236 final boolean changed = !Objects.equals(other.spec, spec) 237 || !Objects.equals(other.icon, icon) 238 || !Objects.equals(other.iconSupplier, iconSupplier) 239 || !Objects.equals(other.label, label) 240 || !Objects.equals(other.secondaryLabel, secondaryLabel) 241 || !Objects.equals(other.contentDescription, contentDescription) 242 || !Objects.equals(other.stateDescription, stateDescription) 243 || !Objects.equals(other.dualLabelContentDescription, 244 dualLabelContentDescription) 245 || !Objects.equals(other.expandedAccessibilityClassName, 246 expandedAccessibilityClassName) 247 || !Objects.equals(other.disabledByPolicy, disabledByPolicy) 248 || !Objects.equals(other.state, state) 249 || !Objects.equals(other.isTransient, isTransient) 250 || !Objects.equals(other.dualTarget, dualTarget) 251 || !Objects.equals(other.handlesLongClick, handlesLongClick) 252 || !Objects.equals(other.handlesSecondaryClick, handlesSecondaryClick) 253 || !Objects.equals(other.sideViewCustomDrawable, sideViewCustomDrawable); 254 other.spec = spec; 255 other.icon = icon; 256 other.iconSupplier = iconSupplier; 257 other.label = label; 258 other.secondaryLabel = secondaryLabel; 259 other.contentDescription = contentDescription; 260 other.stateDescription = stateDescription; 261 other.dualLabelContentDescription = dualLabelContentDescription; 262 other.expandedAccessibilityClassName = expandedAccessibilityClassName; 263 other.disabledByPolicy = disabledByPolicy; 264 other.state = state; 265 other.dualTarget = dualTarget; 266 other.isTransient = isTransient; 267 other.handlesLongClick = handlesLongClick; 268 other.handlesSecondaryClick = handlesSecondaryClick; 269 other.sideViewCustomDrawable = sideViewCustomDrawable; 270 return changed; 271 } 272 273 @Override toString()274 public String toString() { 275 return toStringBuilder().toString(); 276 } 277 278 // Used in dumps to determine current state of a tile. 279 // This string may be used for CTS testing of tiles, so removing elements is discouraged. toStringBuilder()280 protected StringBuilder toStringBuilder() { 281 final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('['); 282 sb.append("spec=").append(spec); 283 sb.append(",icon=").append(icon); 284 sb.append(",iconSupplier=").append(iconSupplier); 285 sb.append(",label=").append(label); 286 sb.append(",secondaryLabel=").append(secondaryLabel); 287 sb.append(",contentDescription=").append(contentDescription); 288 sb.append(",stateDescription=").append(stateDescription); 289 sb.append(",dualLabelContentDescription=").append(dualLabelContentDescription); 290 sb.append(",expandedAccessibilityClassName=").append(expandedAccessibilityClassName); 291 sb.append(",disabledByPolicy=").append(disabledByPolicy); 292 sb.append(",dualTarget=").append(dualTarget); 293 sb.append(",isTransient=").append(isTransient); 294 sb.append(",handlesSecondaryClick=").append(handlesSecondaryClick); 295 sb.append(",state=").append(state); 296 sb.append(",sideViewCustomDrawable=").append(sideViewCustomDrawable); 297 return sb.append(']'); 298 } 299 300 @NonNull copy()301 public State copy() { 302 State state = new State(); 303 copyTo(state); 304 return state; 305 } 306 } 307 308 /** 309 * Distinguished from [BooleanState] for use-case purposes such as allowing null secondary label 310 */ 311 @ProvidesInterface(version = AdapterState.VERSION) 312 class AdapterState extends State { 313 public static final int VERSION = 1; 314 public boolean value; 315 public boolean forceExpandIcon; 316 317 @Override copyTo(State other)318 public boolean copyTo(State other) { 319 final AdapterState o = (AdapterState) other; 320 final boolean changed = super.copyTo(other) 321 || o.value != value 322 || o.forceExpandIcon != forceExpandIcon; 323 o.value = value; 324 o.forceExpandIcon = forceExpandIcon; 325 return changed; 326 } 327 328 @Override toStringBuilder()329 protected StringBuilder toStringBuilder() { 330 final StringBuilder rt = super.toStringBuilder(); 331 rt.insert(rt.length() - 1, ",value=" + value); 332 rt.insert(rt.length() - 1, ",forceExpandIcon=" + forceExpandIcon); 333 return rt; 334 } 335 336 @androidx.annotation.NonNull 337 @Override copy()338 public State copy() { 339 AdapterState state = new AdapterState(); 340 copyTo(state); 341 return state; 342 } 343 } 344 345 @ProvidesInterface(version = BooleanState.VERSION) 346 class BooleanState extends AdapterState { 347 public static final int VERSION = 1; 348 349 @androidx.annotation.NonNull 350 @Override copy()351 public State copy() { 352 BooleanState state = new BooleanState(); 353 copyTo(state); 354 return state; 355 } 356 } 357 } 358