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 android.service.notification; 18 19 import static com.android.internal.util.Preconditions.checkArgument; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.app.Flags; 26 import android.content.Context; 27 import android.net.Uri; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.util.proto.ProtoOutputStream; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.util.Objects; 35 36 /** 37 * The current condition of an {@link android.app.AutomaticZenRule}, provided by the 38 * app that owns the rule. Used to tell the system to enter Do Not 39 * Disturb mode and request that the system exit Do Not Disturb mode. 40 */ 41 public final class Condition implements Parcelable { 42 43 public static final String SCHEME = "condition"; 44 45 /** @hide */ 46 @IntDef(prefix = { "STATE_" }, value = { 47 STATE_FALSE, 48 STATE_TRUE, 49 STATE_UNKNOWN, 50 STATE_ERROR 51 }) 52 @Retention(RetentionPolicy.SOURCE) 53 public @interface State {} 54 55 /** 56 * Indicates that Do Not Disturb should be turned off. Note that all Conditions from all 57 * {@link android.app.AutomaticZenRule} providers must be off for Do Not Disturb to be turned 58 * off on the device. 59 */ 60 public static final int STATE_FALSE = 0; 61 /** 62 * Indicates that Do Not Disturb should be turned on. 63 */ 64 public static final int STATE_TRUE = 1; 65 public static final int STATE_UNKNOWN = 2; 66 public static final int STATE_ERROR = 3; 67 68 public static final int FLAG_RELEVANT_NOW = 1 << 0; 69 public static final int FLAG_RELEVANT_ALWAYS = 1 << 1; 70 71 /** 72 * The URI representing the rule being updated. 73 * See {@link android.app.AutomaticZenRule#getConditionId()}. 74 */ 75 public final Uri id; 76 77 /** 78 * A summary of what the rule encoded in {@link #id} means when it is enabled. User visible 79 * if the state of the condition is {@link #STATE_TRUE}. 80 */ 81 public final String summary; 82 83 public final String line1; 84 public final String line2; 85 86 /** 87 * The state of this condition. {@link #STATE_TRUE} will enable Do Not Disturb mode. 88 * {@link #STATE_FALSE} will turn Do Not Disturb off for this rule. Note that Do Not Disturb 89 * might still be enabled globally if other conditions are in a {@link #STATE_TRUE} state. 90 */ 91 @State 92 public final int state; 93 94 public final int flags; 95 public final int icon; 96 97 /** @hide */ 98 @IntDef(prefix = { "SOURCE_" }, value = { 99 SOURCE_UNKNOWN, 100 SOURCE_USER_ACTION, 101 SOURCE_SCHEDULE, 102 SOURCE_CONTEXT 103 }) 104 @Retention(RetentionPolicy.SOURCE) 105 public @interface Source {} 106 107 /** The state is changing due to an unknown reason. */ 108 @FlaggedApi(Flags.FLAG_MODES_API) 109 public static final int SOURCE_UNKNOWN = 0; 110 /** The state is changing due to an explicit user action. */ 111 @FlaggedApi(Flags.FLAG_MODES_API) 112 public static final int SOURCE_USER_ACTION = 1; 113 /** The state is changing due to an automatic schedule (alarm, set time, etc). */ 114 @FlaggedApi(Flags.FLAG_MODES_API) 115 public static final int SOURCE_SCHEDULE = 2; 116 /** The state is changing due to a change in context (such as detected driving or sleeping). */ 117 @FlaggedApi(Flags.FLAG_MODES_API) 118 public static final int SOURCE_CONTEXT = 3; 119 120 /** The source of, or reason for, the state change represented by this Condition. **/ 121 @FlaggedApi(Flags.FLAG_MODES_API) 122 public final @Source int source; // default = SOURCE_UNKNOWN 123 124 /** 125 * The maximum string length for any string contained in this condition. 126 * @hide 127 */ 128 public static final int MAX_STRING_LENGTH = 1000; 129 130 /** 131 * An object representing the current state of a {@link android.app.AutomaticZenRule}. 132 * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule 133 * @param summary a user visible description of the rule state 134 * @param state whether the mode should be activated or deactivated 135 */ 136 // TODO: b/310208502 - Deprecate this in favor of constructor which specifies source. Condition(Uri id, String summary, int state)137 public Condition(Uri id, String summary, int state) { 138 this(id, summary, "", "", -1, state, SOURCE_UNKNOWN, FLAG_RELEVANT_ALWAYS); 139 } 140 141 /** 142 * An object representing the current state of a {@link android.app.AutomaticZenRule}. 143 * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule 144 * @param summary a user visible description of the rule state 145 * @param state whether the mode should be activated or deactivated 146 * @param source the source of, or reason for, the state change represented by this Condition 147 */ 148 @FlaggedApi(Flags.FLAG_MODES_API) Condition(@ullable Uri id, @Nullable String summary, @State int state, @Source int source)149 public Condition(@Nullable Uri id, @Nullable String summary, @State int state, 150 @Source int source) { 151 this(id, summary, "", "", -1, state, source, FLAG_RELEVANT_ALWAYS); 152 } 153 154 // TODO: b/310208502 - Deprecate this in favor of constructor which specifies source. Condition(Uri id, String summary, String line1, String line2, int icon, int state, int flags)155 public Condition(Uri id, String summary, String line1, String line2, int icon, 156 int state, int flags) { 157 this(id, summary, line1, line2, icon, state, SOURCE_UNKNOWN, flags); 158 } 159 160 /** 161 * An object representing the current state of a {@link android.app.AutomaticZenRule}. 162 * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule 163 * @param summary a user visible description of the rule state 164 * @param line1 a user-visible description of when the rule will end 165 * @param line2 a continuation of the user-visible description of when the rule will end 166 * @param icon an icon representing this condition 167 * @param state whether the mode should be activated or deactivated 168 * @param source the source of, or reason for, the state change represented by this Condition 169 * @param flags flags on this condition 170 */ 171 @FlaggedApi(Flags.FLAG_MODES_API) Condition(@ullable Uri id, @Nullable String summary, @Nullable String line1, @Nullable String line2, int icon, @State int state, @Source int source, int flags)172 public Condition(@Nullable Uri id, @Nullable String summary, @Nullable String line1, 173 @Nullable String line2, int icon, @State int state, @Source int source, 174 int flags) { 175 if (id == null) throw new IllegalArgumentException("id is required"); 176 if (summary == null) throw new IllegalArgumentException("summary is required"); 177 if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state); 178 this.id = getTrimmedUri(id); 179 this.summary = getTrimmedString(summary); 180 this.line1 = getTrimmedString(line1); 181 this.line2 = getTrimmedString(line2); 182 this.icon = icon; 183 this.state = state; 184 this.source = checkValidSource(source); 185 this.flags = flags; 186 } 187 Condition(Parcel source)188 public Condition(Parcel source) { 189 // This constructor passes all fields directly into the constructor that takes all the 190 // fields as arguments; that constructor will trim each of the input strings to 191 // max length if necessary. 192 this((Uri)source.readParcelable(Condition.class.getClassLoader(), android.net.Uri.class), 193 source.readString(), 194 source.readString(), 195 source.readString(), 196 source.readInt(), 197 source.readInt(), 198 Flags.modesApi() ? source.readInt() : SOURCE_UNKNOWN, 199 source.readInt()); 200 } 201 202 /** @hide */ validate()203 public void validate() { 204 if (Flags.modesApi()) { 205 checkValidSource(source); 206 } 207 } 208 isValidState(int state)209 private static boolean isValidState(int state) { 210 return state >= STATE_FALSE && state <= STATE_ERROR; 211 } 212 checkValidSource(@ource int source)213 private static int checkValidSource(@Source int source) { 214 if (Flags.modesApi()) { 215 checkArgument(source >= SOURCE_UNKNOWN && source <= SOURCE_CONTEXT, 216 "Condition source must be one of SOURCE_UNKNOWN, SOURCE_USER_ACTION, " 217 + "SOURCE_SCHEDULE, or SOURCE_CONTEXT"); 218 } 219 return source; 220 } 221 222 @Override writeToParcel(Parcel dest, int flags)223 public void writeToParcel(Parcel dest, int flags) { 224 dest.writeParcelable(id, 0); 225 dest.writeString(summary); 226 dest.writeString(line1); 227 dest.writeString(line2); 228 dest.writeInt(icon); 229 dest.writeInt(state); 230 if (Flags.modesApi()) { 231 dest.writeInt(this.source); 232 } 233 dest.writeInt(this.flags); 234 } 235 236 @Override toString()237 public String toString() { 238 StringBuilder sb = new StringBuilder(Condition.class.getSimpleName()).append('[') 239 .append("state=").append(stateToString(state)) 240 .append(",id=").append(id) 241 .append(",summary=").append(summary) 242 .append(",line1=").append(line1) 243 .append(",line2=").append(line2) 244 .append(",icon=").append(icon); 245 if (Flags.modesApi()) { 246 sb.append(",source=").append(sourceToString(source)); 247 } 248 return sb.append(",flags=").append(flags) 249 .append(']').toString(); 250 251 } 252 253 /** @hide */ dumpDebug(ProtoOutputStream proto, long fieldId)254 public void dumpDebug(ProtoOutputStream proto, long fieldId) { 255 final long token = proto.start(fieldId); 256 257 // id is guaranteed not to be null. 258 proto.write(ConditionProto.ID, id.toString()); 259 proto.write(ConditionProto.SUMMARY, summary); 260 proto.write(ConditionProto.LINE_1, line1); 261 proto.write(ConditionProto.LINE_2, line2); 262 proto.write(ConditionProto.ICON, icon); 263 proto.write(ConditionProto.STATE, state); 264 // TODO: b/310644464 - Add source to dump. 265 proto.write(ConditionProto.FLAGS, flags); 266 267 proto.end(token); 268 } 269 stateToString(int state)270 public static String stateToString(int state) { 271 if (state == STATE_FALSE) return "STATE_FALSE"; 272 if (state == STATE_TRUE) return "STATE_TRUE"; 273 if (state == STATE_UNKNOWN) return "STATE_UNKNOWN"; 274 if (state == STATE_ERROR) return "STATE_ERROR"; 275 throw new IllegalArgumentException("state is invalid: " + state); 276 } 277 278 /** 279 * Provides a human-readable string version of the Source enum. 280 * @hide 281 */ 282 @FlaggedApi(Flags.FLAG_MODES_API) sourceToString(@ource int source)283 public static @NonNull String sourceToString(@Source int source) { 284 if (source == SOURCE_UNKNOWN) return "SOURCE_UNKNOWN"; 285 if (source == SOURCE_USER_ACTION) return "SOURCE_USER_ACTION"; 286 if (source == SOURCE_SCHEDULE) return "SOURCE_SCHEDULE"; 287 if (source == SOURCE_CONTEXT) return "SOURCE_CONTEXT"; 288 throw new IllegalArgumentException("source is invalid: " + source); 289 } 290 relevanceToString(int flags)291 public static String relevanceToString(int flags) { 292 final boolean now = (flags & FLAG_RELEVANT_NOW) != 0; 293 final boolean always = (flags & FLAG_RELEVANT_ALWAYS) != 0; 294 if (!now && !always) return "NONE"; 295 if (now && always) return "NOW, ALWAYS"; 296 return now ? "NOW" : "ALWAYS"; 297 } 298 299 @Override equals(@ullable Object o)300 public boolean equals(@Nullable Object o) { 301 if (!(o instanceof Condition)) return false; 302 if (o == this) return true; 303 final Condition other = (Condition) o; 304 boolean finalEquals = Objects.equals(other.id, id) 305 && Objects.equals(other.summary, summary) 306 && Objects.equals(other.line1, line1) 307 && Objects.equals(other.line2, line2) 308 && other.icon == icon 309 && other.state == state 310 && other.flags == flags; 311 if (Flags.modesApi()) { 312 return finalEquals && other.source == source; 313 } 314 return finalEquals; 315 } 316 317 @Override hashCode()318 public int hashCode() { 319 if (Flags.modesApi()) { 320 return Objects.hash(id, summary, line1, line2, icon, state, source, flags); 321 } 322 return Objects.hash(id, summary, line1, line2, icon, state, flags); 323 } 324 325 @Override describeContents()326 public int describeContents() { 327 return 0; 328 } 329 copy()330 public Condition copy() { 331 final Parcel parcel = Parcel.obtain(); 332 try { 333 writeToParcel(parcel, 0); 334 parcel.setDataPosition(0); 335 return new Condition(parcel); 336 } finally { 337 parcel.recycle(); 338 } 339 } 340 newId(Context context)341 public static Uri.Builder newId(Context context) { 342 return new Uri.Builder() 343 .scheme(Condition.SCHEME) 344 .authority(context.getPackageName()); 345 } 346 isValidId(Uri id, String pkg)347 public static boolean isValidId(Uri id, String pkg) { 348 return id != null && SCHEME.equals(id.getScheme()) && pkg.equals(id.getAuthority()); 349 } 350 351 public static final @android.annotation.NonNull Parcelable.Creator<Condition> CREATOR 352 = new Parcelable.Creator<Condition>() { 353 @Override 354 public Condition createFromParcel(Parcel source) { 355 return new Condition(source); 356 } 357 358 @Override 359 public Condition[] newArray(int size) { 360 return new Condition[size]; 361 } 362 }; 363 364 /** 365 * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH. 366 */ getTrimmedString(String input)367 private static String getTrimmedString(String input) { 368 if (input != null && input.length() > MAX_STRING_LENGTH) { 369 return input.substring(0, MAX_STRING_LENGTH); 370 } 371 return input; 372 } 373 374 /** 375 * Returns a truncated copy of the Uri by trimming the string representation to the maximum 376 * string length. 377 */ getTrimmedUri(Uri input)378 private static Uri getTrimmedUri(Uri input) { 379 if (input != null && input.toString().length() > MAX_STRING_LENGTH) { 380 return Uri.parse(getTrimmedString(input.toString())); 381 } 382 return input; 383 } 384 } 385