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