File: ParcelableResource.java

package info (click to toggle)
android-platform-frameworks-base 1%3A14~beta1-3
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 326,092 kB
  • sloc: java: 2,032,373; xml: 343,016; cpp: 304,181; python: 3,683; ansic: 2,090; sh: 1,871; makefile: 117; sed: 19
file content (362 lines) | stat: -rw-r--r-- 14,653 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app.admin;

import static java.util.Objects.requireNonNull;

import android.annotation.AnyRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.function.Supplier;

/**
 * Used to store the required information to load a resource that was updated using
 * {@link DevicePolicyResourcesManager#setDrawables} and
 * {@link DevicePolicyResourcesManager#setStrings}.
 *
 * @hide
 */
public final class ParcelableResource implements Parcelable {

    private static String TAG = "DevicePolicyManager";

    private static final String ATTR_RESOURCE_ID = "resource-id";
    private static final String ATTR_PACKAGE_NAME = "package-name";
    private static final String ATTR_RESOURCE_NAME = "resource-name";
    private static final String ATTR_RESOURCE_TYPE = "resource-type";

    public static final int RESOURCE_TYPE_DRAWABLE = 1;
    public static final int RESOURCE_TYPE_STRING = 2;


    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = { "RESOURCE_TYPE_" }, value = {
            RESOURCE_TYPE_DRAWABLE,
            RESOURCE_TYPE_STRING
    })
    public @interface ResourceType {}

    private final int mResourceId;
    @NonNull private final String mPackageName;
    @NonNull private final String mResourceName;
    private final int mResourceType;

    /**
     *
     * Creates a {@code ParcelableDevicePolicyResource} for the given {@code resourceId} and
     * verifies that it exists in the package of the given {@code context}.
     *
     * @param context for the package containing the {@code resourceId} to use as the updated
     *                resource
     * @param resourceId of the resource to use as an updated resource
     * @param resourceType see {@link ResourceType}
     */
    public ParcelableResource(
            @NonNull Context context, @AnyRes int resourceId, @ResourceType int resourceType)
            throws IllegalStateException, IllegalArgumentException {
        Objects.requireNonNull(context, "context must be provided");
        verifyResourceExistsInCallingPackage(context, resourceId, resourceType);

        this.mResourceId = resourceId;
        this.mPackageName = context.getResources().getResourcePackageName(resourceId);
        this.mResourceName = context.getResources().getResourceName(resourceId);
        this.mResourceType = resourceType;
    }

    /**
     * Creates a {@code ParcelableDevicePolicyResource} with the given params, this DOES NOT make
     * any verifications on whether the given {@code resourceId} actually exists.
     */
    private ParcelableResource(
            @AnyRes int resourceId, @NonNull String packageName, @NonNull String resourceName,
            @ResourceType int resourceType) {
        this.mResourceId = resourceId;
        this.mPackageName = requireNonNull(packageName);
        this.mResourceName = requireNonNull(resourceName);
        this.mResourceType = resourceType;
    }

    private static void verifyResourceExistsInCallingPackage(
            Context context, @AnyRes int resourceId, @ResourceType int resourceType)
            throws IllegalStateException, IllegalArgumentException {
        switch (resourceType) {
            case RESOURCE_TYPE_DRAWABLE:
                if (!hasDrawableInCallingPackage(context, resourceId)) {
                    throw new IllegalStateException(String.format(
                            "Drawable with id %d doesn't exist in the calling package %s",
                            resourceId,
                            context.getPackageName()));
                }
                break;
            case RESOURCE_TYPE_STRING:
                if (!hasStringInCallingPackage(context, resourceId)) {
                    throw new IllegalStateException(String.format(
                            "String with id %d doesn't exist in the calling package %s",
                            resourceId,
                            context.getPackageName()));
                }
                break;
            default:
                throw new IllegalArgumentException(
                        "Unknown ResourceType: " + resourceType);
        }
    }

    private static boolean hasDrawableInCallingPackage(Context context, @AnyRes int resourceId) {
        try {
            return "drawable".equals(context.getResources().getResourceTypeName(resourceId));
        } catch (Resources.NotFoundException e) {
            return false;
        }
    }

    private static boolean hasStringInCallingPackage(Context context, @AnyRes int resourceId) {
        try {
            return "string".equals(context.getResources().getResourceTypeName(resourceId));
        } catch (Resources.NotFoundException e) {
            return false;
        }
    }

    public @AnyRes int getResourceId() {
        return mResourceId;
    }

    @NonNull
    public String getPackageName() {
        return mPackageName;
    }

    @NonNull
    public String getResourceName() {
        return mResourceName;
    }

    public int getResourceType() {
        return mResourceType;
    }

    /**
     * Loads the drawable with id {@code mResourceId} from {@code mPackageName} using the provided
     * {@code density} and {@link Resources.Theme} and {@link Resources#getConfiguration} of the
     * provided {@code context}.
     *
     * <p>Returns the default drawable by calling the {@code defaultDrawableLoader} if the updated
     * drawable was not found or could not be loaded.</p>
     */
    @Nullable
    public Drawable getDrawable(
            Context context,
            int density,
            @NonNull Supplier<Drawable> defaultDrawableLoader) {
        // TODO(b/203548565): properly handle edge case when the device manager role holder is
        //  unavailable because it's being updated.
        try {
            Resources resources = getAppResourcesWithCallersConfiguration(context);
            verifyResourceName(resources);
            return resources.getDrawableForDensity(mResourceId, density, context.getTheme());
        } catch (PackageManager.NameNotFoundException | RuntimeException e) {
            Slog.e(TAG, "Unable to load drawable resource " + mResourceName, e);
            return loadDefaultDrawable(defaultDrawableLoader);
        }
    }

    /**
     * Loads the string with id {@code mResourceId} from {@code mPackageName} using the
     * configuration returned from {@link Resources#getConfiguration} of the provided
     * {@code context}.
     *
     * <p>Returns the default string by calling  {@code defaultStringLoader} if the updated
     * string was not found or could not be loaded.</p>
     */
    @Nullable
    public String getString(
            Context context,
            @NonNull Supplier<String> defaultStringLoader) {
        // TODO(b/203548565): properly handle edge case when the device manager role holder is
        //  unavailable because it's being updated.
        try {
            Resources resources = getAppResourcesWithCallersConfiguration(context);
            verifyResourceName(resources);
            return resources.getString(mResourceId);
        } catch (PackageManager.NameNotFoundException | RuntimeException e) {
            Slog.e(TAG, "Unable to load string resource " + mResourceName, e);
            return loadDefaultString(defaultStringLoader);
        }
    }

    /**
     * Loads the string with id {@code mResourceId} from {@code mPackageName} using the
     * configuration returned from {@link Resources#getConfiguration} of the provided
     * {@code context}.
     *
     * <p>Returns the default string by calling  {@code defaultStringLoader} if the updated
     * string was not found or could not be loaded.</p>
     */
    @Nullable
    public String getString(
            Context context,
            @NonNull Supplier<String> defaultStringLoader,
            @NonNull Object... formatArgs) {
        // TODO(b/203548565): properly handle edge case when the device manager role holder is
        //  unavailable because it's being updated.
        try {
            Resources resources = getAppResourcesWithCallersConfiguration(context);
            verifyResourceName(resources);
            String rawString = resources.getString(mResourceId);
            return String.format(
                    context.getResources().getConfiguration().getLocales().get(0),
                    rawString,
                    formatArgs);
        } catch (PackageManager.NameNotFoundException | RuntimeException e) {
            Slog.e(TAG, "Unable to load string resource " + mResourceName, e);
            return loadDefaultString(defaultStringLoader);
        }
    }

    private Resources getAppResourcesWithCallersConfiguration(Context context)
            throws PackageManager.NameNotFoundException {
        PackageManager pm = context.getPackageManager();
        ApplicationInfo ai = pm.getApplicationInfo(
                mPackageName,
                PackageManager.MATCH_UNINSTALLED_PACKAGES
                        | PackageManager.GET_SHARED_LIBRARY_FILES);
        return pm.getResourcesForApplication(ai, context.getResources().getConfiguration());
    }

    private void verifyResourceName(Resources resources) throws IllegalStateException {
        String name = resources.getResourceName(mResourceId);
        if (!mResourceName.equals(name)) {
            throw new IllegalStateException(String.format("Current resource name %s for resource id"
                            + " %d has changed from the previously stored resource name %s.",
                    name, mResourceId, mResourceName));
        }
    }

    /**
     * returns the {@link Drawable} loaded from calling {@code defaultDrawableLoader}.
     */
    @Nullable
    public static Drawable loadDefaultDrawable(@NonNull Supplier<Drawable> defaultDrawableLoader) {
        Objects.requireNonNull(defaultDrawableLoader, "defaultDrawableLoader can't be null");
        return defaultDrawableLoader.get();
    }

    /**
     * returns the {@link String} loaded from calling {@code defaultStringLoader}.
     */
    @Nullable
    public static String loadDefaultString(@NonNull Supplier<String> defaultStringLoader) {
        Objects.requireNonNull(defaultStringLoader, "defaultStringLoader can't be null");
        return defaultStringLoader.get();
    }

    /**
     * Writes the content of the current {@code ParcelableDevicePolicyResource} to the xml file
     * specified by {@code xmlSerializer}.
     */
    public void writeToXmlFile(TypedXmlSerializer xmlSerializer) throws IOException {
        xmlSerializer.attributeInt(/* namespace= */ null, ATTR_RESOURCE_ID, mResourceId);
        xmlSerializer.attribute(/* namespace= */ null, ATTR_PACKAGE_NAME, mPackageName);
        xmlSerializer.attribute(/* namespace= */ null, ATTR_RESOURCE_NAME, mResourceName);
        xmlSerializer.attributeInt(/* namespace= */ null, ATTR_RESOURCE_TYPE, mResourceType);
    }

    /**
     * Creates a new {@code ParcelableDevicePolicyResource} using the content of
     * {@code xmlPullParser}.
     */
    public static ParcelableResource createFromXml(TypedXmlPullParser xmlPullParser)
            throws XmlPullParserException, IOException {
        int resourceId = xmlPullParser.getAttributeInt(/* namespace= */ null, ATTR_RESOURCE_ID);
        String packageName = xmlPullParser.getAttributeValue(
                /* namespace= */ null, ATTR_PACKAGE_NAME);
        String resourceName = xmlPullParser.getAttributeValue(
                /* namespace= */ null, ATTR_RESOURCE_NAME);
        int resourceType = xmlPullParser.getAttributeInt(
                /* namespace= */ null, ATTR_RESOURCE_TYPE);

        return new ParcelableResource(
                resourceId, packageName, resourceName, resourceType);
    }

    @Override
    public boolean equals(@Nullable Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ParcelableResource other = (ParcelableResource) o;
        return mResourceId == other.mResourceId
                && mPackageName.equals(other.mPackageName)
                && mResourceName.equals(other.mResourceName)
                && mResourceType == other.mResourceType;
    }

    @Override
    public int hashCode() {
        return Objects.hash(mResourceId, mPackageName, mResourceName, mResourceType);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(mResourceId);
        dest.writeString(mPackageName);
        dest.writeString(mResourceName);
        dest.writeInt(mResourceType);
    }

    public static final @NonNull Creator<ParcelableResource> CREATOR =
            new Creator<ParcelableResource>() {
                @Override
                public ParcelableResource createFromParcel(Parcel in) {
                    int resourceId = in.readInt();
                    String packageName = in.readString();
                    String resourceName = in.readString();
                    int resourceType = in.readInt();

                    return new ParcelableResource(
                            resourceId, packageName, resourceName, resourceType);
                }

                @Override
                public ParcelableResource[] newArray(int size) {
                    return new ParcelableResource[size];
                }
            };
}