• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package android.content.pm;
18 
19 import static android.content.res.Resources.ID_NULL;
20 
21 import android.annotation.DrawableRes;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.StringRes;
25 import android.annotation.SystemApi;
26 import android.content.res.ResourceId;
27 import android.os.Parcel;
28 import android.os.Parcelable;
29 import android.os.PersistableBundle;
30 import android.util.Slog;
31 
32 import com.android.internal.util.Preconditions;
33 import com.android.internal.util.XmlUtils;
34 
35 import org.xmlpull.v1.XmlPullParser;
36 import org.xmlpull.v1.XmlSerializer;
37 
38 import java.io.IOException;
39 import java.util.Locale;
40 import 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
63 public 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
getIconResId()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
getTitleResId()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
getDialogMessageResId()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
getDialogMessage()110     public String getDialogMessage() {
111         return mDialogMessage;
112     }
113 
114     /**
115      * @return the text to be shown
116      * @hide
117      */
118     @StringRes
getNeutralButtonTextResId()119     public int getNeutralButtonTextResId() {
120         return mNeutralButtonTextResId;
121     }
122 
123     /**
124      * @hide
125      */
saveToXml(XmlSerializer out)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      */
restoreFromXml(XmlPullParser in)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
hashCode()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
equals(Object obj)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
toString()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
describeContents()235     public int describeContents() {
236         return 0;
237     }
238 
239     @Override
writeToParcel(Parcel dest, int parcelableFlags)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 
SuspendDialogInfo(Parcel source)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 
SuspendDialogInfo(Builder b)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 
264     public static final @android.annotation.NonNull Creator<SuspendDialogInfo> CREATOR = new Creator<SuspendDialogInfo>() {
265         @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
setIcon(@rawableRes int resId)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
setTitle(@tringRes int resId)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
setMessage(@onNull String message)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
setMessage(@tringRes int resId)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
setNeutralButtonText(@tringRes int resId)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
build()375         public SuspendDialogInfo build() {
376             return new SuspendDialogInfo(this);
377         }
378     }
379 }
380