blob: db8f8c2387d60746924ad1aa95159ad2d8626d0d [file] [log] [blame]
Suprabh Shukla389cb6f2018-10-01 18:20:39 -07001/*
2 * Copyright (C) 2018 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
17package android.content.pm;
18
Aurimas Liutikasd8ebfef2019-01-16 12:46:42 -080019import static android.content.res.Resources.ID_NULL;
Suprabh Shukla389cb6f2018-10-01 18:20:39 -070020
21import android.annotation.DrawableRes;
22import android.annotation.NonNull;
23import android.annotation.Nullable;
24import android.annotation.StringRes;
25import android.annotation.SystemApi;
26import android.content.res.ResourceId;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.os.PersistableBundle;
30import android.util.Slog;
31
32import com.android.internal.util.Preconditions;
33import com.android.internal.util.XmlUtils;
34
35import org.xmlpull.v1.XmlPullParser;
36import org.xmlpull.v1.XmlSerializer;
37
38import java.io.IOException;
39import java.util.Locale;
40import java.util.Objects;
41
42/**
43 * A container to describe the dialog to be shown when the user tries to launch a suspended
44 * application.
45 * The suspending app can customize the dialog's following attributes:
46 * <ul>
47 * <li>The dialog icon, by providing a resource id.
48 * <li>The title text, by providing a resource id.
49 * <li>The text of the dialog's body, by providing a resource id or a string.
50 * <li>The text on the neutral button which starts the
51 * {@link android.content.Intent#ACTION_SHOW_SUSPENDED_APP_DETAILS SHOW_SUSPENDED_APP_DETAILS}
52 * activity, by providing a resource id.
53 * </ul>
54 * System defaults are used whenever any of these are not provided, or any of the provided resource
55 * ids cannot be resolved at the time of displaying the dialog.
56 *
57 * @hide
58 * @see PackageManager#setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle,
59 * SuspendDialogInfo)
60 * @see Builder
61 */
62@SystemApi
63public final class SuspendDialogInfo implements Parcelable {
64 private static final String TAG = SuspendDialogInfo.class.getSimpleName();
65 private static final String XML_ATTR_ICON_RES_ID = "iconResId";
66 private static final String XML_ATTR_TITLE_RES_ID = "titleResId";
67 private static final String XML_ATTR_DIALOG_MESSAGE_RES_ID = "dialogMessageResId";
68 private static final String XML_ATTR_DIALOG_MESSAGE = "dialogMessage";
69 private static final String XML_ATTR_BUTTON_TEXT_RES_ID = "buttonTextResId";
70
71 private final int mIconResId;
72 private final int mTitleResId;
73 private final int mDialogMessageResId;
74 private final String mDialogMessage;
75 private final int mNeutralButtonTextResId;
76
77 /**
78 * @return the resource id of the icon to be used with the dialog
79 * @hide
80 */
81 @DrawableRes
82 public int getIconResId() {
83 return mIconResId;
84 }
85
86 /**
87 * @return the resource id of the title to be used with the dialog
88 * @hide
89 */
90 @StringRes
91 public int getTitleResId() {
92 return mTitleResId;
93 }
94
95 /**
96 * @return the resource id of the text to be shown in the dialog's body
97 * @hide
98 */
99 @StringRes
100 public int getDialogMessageResId() {
101 return mDialogMessageResId;
102 }
103
104 /**
105 * @return the text to be shown in the dialog's body. Returns {@code null} if
106 * {@link #getDialogMessageResId()} returns a valid resource id.
107 * @hide
108 */
109 @Nullable
110 public String getDialogMessage() {
111 return mDialogMessage;
112 }
113
114 /**
115 * @return the text to be shown
116 * @hide
117 */
118 @StringRes
119 public int getNeutralButtonTextResId() {
120 return mNeutralButtonTextResId;
121 }
122
123 /**
124 * @hide
125 */
126 public void saveToXml(XmlSerializer out) throws IOException {
127 if (mIconResId != ID_NULL) {
128 XmlUtils.writeIntAttribute(out, XML_ATTR_ICON_RES_ID, mIconResId);
129 }
130 if (mTitleResId != ID_NULL) {
131 XmlUtils.writeIntAttribute(out, XML_ATTR_TITLE_RES_ID, mTitleResId);
132 }
133 if (mDialogMessageResId != ID_NULL) {
134 XmlUtils.writeIntAttribute(out, XML_ATTR_DIALOG_MESSAGE_RES_ID, mDialogMessageResId);
135 } else {
136 XmlUtils.writeStringAttribute(out, XML_ATTR_DIALOG_MESSAGE, mDialogMessage);
137 }
138 if (mNeutralButtonTextResId != ID_NULL) {
139 XmlUtils.writeIntAttribute(out, XML_ATTR_BUTTON_TEXT_RES_ID, mNeutralButtonTextResId);
140 }
141 }
142
143 /**
144 * @hide
145 */
146 public static SuspendDialogInfo restoreFromXml(XmlPullParser in) {
147 final SuspendDialogInfo.Builder dialogInfoBuilder = new SuspendDialogInfo.Builder();
148 try {
149 final int iconId = XmlUtils.readIntAttribute(in, XML_ATTR_ICON_RES_ID, ID_NULL);
150 final int titleId = XmlUtils.readIntAttribute(in, XML_ATTR_TITLE_RES_ID, ID_NULL);
151 final int buttonTextId = XmlUtils.readIntAttribute(in, XML_ATTR_BUTTON_TEXT_RES_ID,
152 ID_NULL);
153 final int dialogMessageResId = XmlUtils.readIntAttribute(
154 in, XML_ATTR_DIALOG_MESSAGE_RES_ID, ID_NULL);
155 final String dialogMessage = XmlUtils.readStringAttribute(in, XML_ATTR_DIALOG_MESSAGE);
156
157 if (iconId != ID_NULL) {
158 dialogInfoBuilder.setIcon(iconId);
159 }
160 if (titleId != ID_NULL) {
161 dialogInfoBuilder.setTitle(titleId);
162 }
163 if (buttonTextId != ID_NULL) {
164 dialogInfoBuilder.setNeutralButtonText(buttonTextId);
165 }
166 if (dialogMessageResId != ID_NULL) {
167 dialogInfoBuilder.setMessage(dialogMessageResId);
168 } else if (dialogMessage != null) {
169 dialogInfoBuilder.setMessage(dialogMessage);
170 }
171 } catch (Exception e) {
172 Slog.e(TAG, "Exception while parsing from xml. Some fields may default", e);
173 }
174 return dialogInfoBuilder.build();
175 }
176
177 @Override
178 public int hashCode() {
179 int hashCode = mIconResId;
180 hashCode = 31 * hashCode + mTitleResId;
181 hashCode = 31 * hashCode + mNeutralButtonTextResId;
182 hashCode = 31 * hashCode + mDialogMessageResId;
183 hashCode = 31 * hashCode + Objects.hashCode(mDialogMessage);
184 return hashCode;
185 }
186
187 @Override
188 public boolean equals(Object obj) {
189 if (this == obj) {
190 return true;
191 }
192 if (!(obj instanceof SuspendDialogInfo)) {
193 return false;
194 }
195 final SuspendDialogInfo otherDialogInfo = (SuspendDialogInfo) obj;
196 return mIconResId == otherDialogInfo.mIconResId
197 && mTitleResId == otherDialogInfo.mTitleResId
198 && mDialogMessageResId == otherDialogInfo.mDialogMessageResId
199 && mNeutralButtonTextResId == otherDialogInfo.mNeutralButtonTextResId
200 && Objects.equals(mDialogMessage, otherDialogInfo.mDialogMessage);
201 }
202
203 @Override
204 public String toString() {
205 final StringBuilder builder = new StringBuilder("SuspendDialogInfo: {");
206 if (mIconResId != ID_NULL) {
207 builder.append("mIconId = 0x");
208 builder.append(Integer.toHexString(mIconResId));
209 builder.append(" ");
210 }
211 if (mTitleResId != ID_NULL) {
212 builder.append("mTitleResId = 0x");
213 builder.append(Integer.toHexString(mTitleResId));
214 builder.append(" ");
215 }
216 if (mNeutralButtonTextResId != ID_NULL) {
217 builder.append("mNeutralButtonTextResId = 0x");
218 builder.append(Integer.toHexString(mNeutralButtonTextResId));
219 builder.append(" ");
220 }
221 if (mDialogMessageResId != ID_NULL) {
222 builder.append("mDialogMessageResId = 0x");
223 builder.append(Integer.toHexString(mDialogMessageResId));
224 builder.append(" ");
225 } else if (mDialogMessage != null) {
226 builder.append("mDialogMessage = \"");
227 builder.append(mDialogMessage);
228 builder.append("\" ");
229 }
230 builder.append("}");
231 return builder.toString();
232 }
233
234 @Override
235 public int describeContents() {
236 return 0;
237 }
238
239 @Override
240 public void writeToParcel(Parcel dest, int parcelableFlags) {
241 dest.writeInt(mIconResId);
242 dest.writeInt(mTitleResId);
243 dest.writeInt(mDialogMessageResId);
244 dest.writeString(mDialogMessage);
245 dest.writeInt(mNeutralButtonTextResId);
246 }
247
248 private SuspendDialogInfo(Parcel source) {
249 mIconResId = source.readInt();
250 mTitleResId = source.readInt();
251 mDialogMessageResId = source.readInt();
252 mDialogMessage = source.readString();
253 mNeutralButtonTextResId = source.readInt();
254 }
255
256 SuspendDialogInfo(Builder b) {
257 mIconResId = b.mIconResId;
258 mTitleResId = b.mTitleResId;
259 mDialogMessageResId = b.mDialogMessageResId;
260 mDialogMessage = (mDialogMessageResId == ID_NULL) ? b.mDialogMessage : null;
261 mNeutralButtonTextResId = b.mNeutralButtonTextResId;
262 }
263
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700264 public static final @android.annotation.NonNull Creator<SuspendDialogInfo> CREATOR = new Creator<SuspendDialogInfo>() {
Suprabh Shukla389cb6f2018-10-01 18:20:39 -0700265 @Override
266 public SuspendDialogInfo createFromParcel(Parcel source) {
267 return new SuspendDialogInfo(source);
268 }
269
270 @Override
271 public SuspendDialogInfo[] newArray(int size) {
272 return new SuspendDialogInfo[size];
273 }
274 };
275
276 /**
277 * Builder to build a {@link SuspendDialogInfo} object.
278 */
279 public static final class Builder {
280 private int mDialogMessageResId = ID_NULL;
281 private String mDialogMessage;
282 private int mTitleResId = ID_NULL;
283 private int mIconResId = ID_NULL;
284 private int mNeutralButtonTextResId = ID_NULL;
285
286 /**
287 * Set the resource id of the icon to be used. If not provided, no icon will be shown.
288 *
289 * @param resId The resource id of the icon.
290 * @return this builder object.
291 */
292 @NonNull
293 public Builder setIcon(@DrawableRes int resId) {
294 Preconditions.checkArgument(ResourceId.isValid(resId), "Invalid resource id provided");
295 mIconResId = resId;
296 return this;
297 }
298
299 /**
300 * Set the resource id of the title text to be displayed. If this is not provided, the
301 * system will use a default title.
302 *
303 * @param resId The resource id of the title.
304 * @return this builder object.
305 */
306 @NonNull
307 public Builder setTitle(@StringRes int resId) {
308 Preconditions.checkArgument(ResourceId.isValid(resId), "Invalid resource id provided");
309 mTitleResId = resId;
310 return this;
311 }
312
313 /**
314 * Set the text to show in the body of the dialog. Ignored if a resource id is set via
315 * {@link #setMessage(int)}.
316 * <p>
317 * The system will use {@link String#format(Locale, String, Object...) String.format} to
318 * insert the suspended app name into the message, so an example format string could be
319 * {@code "The app %1$s is currently suspended"}. This is optional - if the string passed in
320 * {@code message} does not accept an argument, it will be used as is.
321 *
322 * @param message The dialog message.
323 * @return this builder object.
324 * @see #setMessage(int)
325 */
326 @NonNull
327 public Builder setMessage(@NonNull String message) {
328 Preconditions.checkStringNotEmpty(message, "Message cannot be null or empty");
329 mDialogMessage = message;
330 return this;
331 }
332
333 /**
334 * Set the resource id of the dialog message to be shown. If no dialog message is provided
335 * via either this method or {@link #setMessage(String)}, the system will use a
336 * default message.
337 * <p>
338 * The system will use {@link android.content.res.Resources#getString(int, Object...)
339 * getString} to insert the suspended app name into the message, so an example format string
340 * could be {@code "The app %1$s is currently suspended"}. This is optional - if the string
341 * referred to by {@code resId} does not accept an argument, it will be used as is.
342 *
343 * @param resId The resource id of the dialog message.
344 * @return this builder object.
345 * @see #setMessage(String)
346 */
347 @NonNull
348 public Builder setMessage(@StringRes int resId) {
349 Preconditions.checkArgument(ResourceId.isValid(resId), "Invalid resource id provided");
350 mDialogMessageResId = resId;
351 return this;
352 }
353
354 /**
355 * Set the resource id of text to be shown on the neutral button. Tapping this button starts
356 * the {@link android.content.Intent#ACTION_SHOW_SUSPENDED_APP_DETAILS} activity. If this is
357 * not provided, the system will use a default text.
358 *
359 * @param resId The resource id of the button text
360 * @return this builder object.
361 */
362 @NonNull
363 public Builder setNeutralButtonText(@StringRes int resId) {
364 Preconditions.checkArgument(ResourceId.isValid(resId), "Invalid resource id provided");
365 mNeutralButtonTextResId = resId;
366 return this;
367 }
368
369 /**
370 * Build the final object based on given inputs.
371 *
372 * @return The {@link SuspendDialogInfo} object built using this builder.
373 */
374 @NonNull
375 public SuspendDialogInfo build() {
376 return new SuspendDialogInfo(this);
377 }
378 }
379}