Automatic sources dropoff on 2020-06-10 18:32:38.095721

The change is generated with prebuilt drop tool.

Change-Id: I24cbf6ba6db262a1ae1445db1427a08fee35b3b4
diff --git a/com/android/car/setupwizardlib/partner/PartnerConfig.java b/com/android/car/setupwizardlib/partner/PartnerConfig.java
new file mode 100644
index 0000000..8946b7a
--- /dev/null
+++ b/com/android/car/setupwizardlib/partner/PartnerConfig.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 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 com.android.car.setupwizardlib.partner;
+
+/** Resources that can be customized by partner overlay APK. */
+public enum PartnerConfig {
+
+    CONFIG_TOOLBAR_BG_COLOR(
+            PartnerConfigKey.KEY_TOOLBAR_BG_COLOR, ResourceType.COLOR),
+
+    CONFIG_TOOLBAR_BUTTON_ICON_BACK(
+            PartnerConfigKey.KEY_TOOLBAR_BUTTON_ICON_BACK, ResourceType.DRAWABLE),
+
+    CONFIG_TOOLBAR_BUTTON_FONT_FAMILY(
+            PartnerConfigKey.KEY_TOOLBAR_BUTTON_FONT_FAMILY, ResourceType.STRING),
+
+    CONFIG_TOOLBAR_BUTTON_PADDING_HORIZONTAL(
+            PartnerConfigKey.KEY_TOOLBAR_BUTTON_PADDING_HORIZONTAL, ResourceType.DIMENSION),
+
+    CONFIG_TOOLBAR_BUTTON_PADDING_VERTICAL(
+            PartnerConfigKey.KEY_TOOLBAR_BUTTON_PADDING_VERTICAL, ResourceType.DIMENSION),
+
+    CONFIG_TOOLBAR_BUTTON_RADIUS(
+            PartnerConfigKey.KEY_TOOLBAR_BUTTON_RADIUS, ResourceType.DIMENSION),
+
+    CONFIG_TOOLBAR_BUTTON_SPACING(
+            PartnerConfigKey.KEY_TOOLBAR_BUTTON_SPACING, ResourceType.DIMENSION),
+
+    CONFIG_TOOLBAR_BUTTON_TEXT_SIZE(
+            PartnerConfigKey.KEY_TOOLBAR_BUTTON_TEXT_SIZE, ResourceType.DIMENSION),
+
+    CONFIG_TOOLBAR_PRIMARY_BUTTON_BG(
+            PartnerConfigKey.KEY_TOOLBAR_PRIMARY_BUTTON_BG, ResourceType.DRAWABLE),
+
+    CONFIG_TOOLBAR_PRIMARY_BUTTON_BG_COLOR(
+            PartnerConfigKey.KEY_TOOLBAR_PRIMARY_BUTTON_BG_COLOR, ResourceType.COLOR),
+
+    CONFIG_TOOLBAR_PRIMARY_BUTTON_TEXT_COLOR(
+            PartnerConfigKey.KEY_TOOLBAR_PRIMARY_BUTTON_TEXT_COLOR, ResourceType.COLOR),
+
+    CONFIG_TOOLBAR_SECONDARY_BUTTON_BG(
+            PartnerConfigKey.KEY_TOOLBAR_SECONDARY_BUTTON_BG, ResourceType.DRAWABLE),
+
+    CONFIG_TOOLBAR_SECONDARY_BUTTON_BG_COLOR(
+            PartnerConfigKey.KEY_TOOLBAR_SECONDARY_BUTTON_BG_COLOR, ResourceType.COLOR),
+
+    CONFIG_TOOLBAR_SECONDARY_BUTTON_TEXT_COLOR(
+            PartnerConfigKey.KEY_TOOLBAR_SECONDARY_BUTTON_TEXT_COLOR, ResourceType.COLOR),
+
+    CONFIG_TOOLBAR_DIVIDER_BG(
+            PartnerConfigKey.KEY_TOOLBAR_DIVIDER_BG, ResourceType.DRAWABLE),
+
+    CONFIG_TOOLBAR_DIVIDER_LINE_WEIGHT(
+            PartnerConfigKey.KEY_TOOLBAR_DIVIDER_LINE_WEIGHT, ResourceType.DIMENSION),
+
+    CONFIG_LOADING_INDICATOR_COLOR(
+            PartnerConfigKey.KEY_LOADING_INDICATOR_COLOR, ResourceType.COLOR),
+
+    CONFIG_LOADING_INDICATOR_LINE_WEIGHT(
+            PartnerConfigKey.KEY_LOADING_INDICATOR_LINE_WEIGHT, ResourceType.DIMENSION),
+
+    CONFIG_LAYOUT_BG_COLOR(
+            PartnerConfigKey.KEY_LAYOUT_BG_COLOR, ResourceType.COLOR);
+
+    public enum ResourceType {
+        COLOR,
+        DRAWABLE,
+        STRING,
+        DIMENSION,
+        BOOLEAN,
+    }
+
+    private final String mResourceName;
+    private final ResourceType mResourceType;
+
+    public ResourceType getResourceType() {
+        return mResourceType;
+    }
+
+    public String getResourceName() {
+        return mResourceName;
+    }
+
+    PartnerConfig(@PartnerConfigKey String resourceName, ResourceType type) {
+        this.mResourceName = resourceName;
+        this.mResourceType = type;
+    }
+}
diff --git a/com/android/car/setupwizardlib/partner/PartnerConfigHelper.java b/com/android/car/setupwizardlib/partner/PartnerConfigHelper.java
new file mode 100644
index 0000000..2f367a8
--- /dev/null
+++ b/com/android/car/setupwizardlib/partner/PartnerConfigHelper.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2019 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 com.android.car.setupwizardlib.partner;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.EnumMap;
+
+/** The helper reads and caches the partner configurations from Car Setup Wizard. */
+public class PartnerConfigHelper {
+
+    private static final String TAG = PartnerConfigHelper.class.getSimpleName();
+
+    @VisibleForTesting
+    static final String SUW_AUTHORITY = "com.google.android.car.setupwizard.partner";
+
+    @VisibleForTesting
+    static final String SUW_GET_PARTNER_CONFIG_METHOD = "getOverlayConfig";
+    private static volatile PartnerConfigHelper sInstance = null;
+
+    @VisibleForTesting Bundle mResultBundle = null;
+
+    @VisibleForTesting
+    final EnumMap<PartnerConfig, Object> mPartnerResourceCache = new EnumMap<>(PartnerConfig.class);
+
+    /** Factory method to get an instance */
+    public static PartnerConfigHelper get(@NonNull Context context) {
+        if (sInstance == null) {
+            synchronized (PartnerConfigHelper.class) {
+                if (sInstance == null) {
+                    sInstance = new PartnerConfigHelper(context);
+                }
+            }
+        }
+        return sInstance;
+    }
+
+    /**
+     * Returns the color of given {@code partnerConfig}, or 0 if the given {@code partnerConfig}
+     * is not found. If the {@code ResourceType} of the given {@code partnerConfig} is not color,
+     * IllegalArgumentException will be thrown.
+     *
+     * @param context The context of client activity
+     * @param partnerConfig The {@code PartnerConfig} of target resource
+     */
+    @ColorInt
+    public int getColor(@NonNull Context context, PartnerConfig partnerConfig) {
+        if (partnerConfig.getResourceType() != PartnerConfig.ResourceType.COLOR) {
+            throw new IllegalArgumentException("Not a color resource");
+        }
+
+        if (mPartnerResourceCache.containsKey(partnerConfig)) {
+            return (int) mPartnerResourceCache.get(partnerConfig);
+        }
+
+        int result = 0;
+        try {
+            String resourceName = partnerConfig.getResourceName();
+            ResourceEntry resourceEntry = getResourceEntryFromKey(resourceName);
+            if (resourceEntry == null) {
+                Log.w(TAG, "Resource not found: " + resourceName);
+                return 0;
+            }
+
+            Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName());
+            result = resource.getColor(resourceEntry.getResourceId(), null);
+            mPartnerResourceCache.put(partnerConfig, result);
+        } catch (PackageManager.NameNotFoundException exception) {
+            Log.e(TAG, exception.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * Returns the {@code Drawable} of given {@code partnerConfig}, or {@code null} if the given
+     * {@code partnerConfig} is not found. If the {@code ResourceType} of the given {@code
+     * resourceConfig} is not drawable, IllegalArgumentException will be thrown.
+     *
+     * @param context The context of client activity
+     * @param partnerConfig The {@code PartnerConfig} of target resource
+     */
+    @Nullable
+    public Drawable getDrawable(@NonNull Context context, PartnerConfig partnerConfig) {
+        if (partnerConfig.getResourceType() != PartnerConfig.ResourceType.DRAWABLE) {
+            throw new IllegalArgumentException("Not a drawable resource");
+        }
+
+        if (mPartnerResourceCache.containsKey(partnerConfig)) {
+            return (Drawable) mPartnerResourceCache.get(partnerConfig);
+        }
+
+        Drawable result = null;
+        try {
+            String resourceName = partnerConfig.getResourceName();
+            ResourceEntry resourceEntry = getResourceEntryFromKey(resourceName);
+            if (resourceEntry == null) {
+                Log.w(TAG, "Resource not found: " + resourceName);
+                return null;
+            }
+            Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName());
+
+            // for @null
+            TypedValue outValue = new TypedValue();
+            resource.getValue(resourceEntry.getResourceId(), outValue, true);
+            if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) {
+                return result;
+            }
+
+            result = resource.getDrawable(resourceEntry.getResourceId(), null);
+            mPartnerResourceCache.put(partnerConfig, result);
+        } catch (PackageManager.NameNotFoundException | NotFoundException exception) {
+            Log.e(TAG, exception.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * Returns the string of the given {@code partnerConfig}, or {@code null} if the given {@code
+     * resourceConfig} is not found. If the {@code ResourceType} of the given {@code partnerConfig}
+     * is not string, IllegalArgumentException will be thrown.
+     *
+     * @param context The context of client activity
+     * @param partnerConfig The {@code PartnerConfig} of target resource
+     */
+    @Nullable
+    public String getString(@NonNull Context context, PartnerConfig partnerConfig) {
+        if (partnerConfig.getResourceType() != PartnerConfig.ResourceType.STRING) {
+            throw new IllegalArgumentException("Not a string resource");
+        }
+
+        if (mPartnerResourceCache.containsKey(partnerConfig)) {
+            return (String) mPartnerResourceCache.get(partnerConfig);
+        }
+
+        String result = null;
+        try {
+            String resourceName = partnerConfig.getResourceName();
+            ResourceEntry resourceEntry = getResourceEntryFromKey(resourceName);
+            if (resourceEntry == null) {
+                Log.w(TAG, "Resource not found: " + resourceName);
+                return null;
+            }
+            Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName());
+            result = resource.getString(resourceEntry.getResourceId());
+            mPartnerResourceCache.put(partnerConfig, result);
+        } catch (PackageManager.NameNotFoundException exception) {
+            Log.e(TAG, exception.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * Returns the dimension of given {@code partnerConfig}. The default return value is 0.
+     *
+     * @param context The context of client activity
+     * @param resourceConfig The {@code PartnerConfig} of target resource
+     */
+    public float getDimension(@NonNull Context context, PartnerConfig resourceConfig) {
+        return getDimension(context, resourceConfig, 0);
+    }
+
+    /**
+     * Returns the dimension of given {@code partnerConfig}. If the given {@code partnerConfig}
+     * not found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code
+     * resourceConfig} is not dimension, will throw IllegalArgumentException.
+     *
+     * @param context The context of client activity
+     * @param partnerConfig The {@code PartnerConfig} of target resource
+     * @param defaultValue The default value
+     */
+    public float getDimension(
+            @NonNull Context context, PartnerConfig partnerConfig, float defaultValue) {
+        if (partnerConfig.getResourceType() != PartnerConfig.ResourceType.DIMENSION) {
+            throw new IllegalArgumentException("Not a dimension resource");
+        }
+
+        if (mPartnerResourceCache.containsKey(partnerConfig)) {
+            return (float) mPartnerResourceCache.get(partnerConfig);
+        }
+
+        float result = defaultValue;
+        try {
+            String resourceName = partnerConfig.getResourceName();
+            ResourceEntry resourceEntry = getResourceEntryFromKey(resourceName);
+            if (resourceEntry == null) {
+                Log.w(TAG, "Resource not found: " + resourceName);
+                return defaultValue;
+            }
+            Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName());
+            result = resource.getDimension(resourceEntry.getResourceId());
+            mPartnerResourceCache.put(partnerConfig, result);
+        } catch (PackageManager.NameNotFoundException exception) {
+            Log.e(TAG, exception.getMessage());
+        }
+        return result;
+    }
+
+    /**
+     * Returns the boolean value of given {@code partnerConfig}. If the given {@code partnerConfig}
+     * not found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code
+     * resourceConfig} is not boolean, will throw IllegalArgumentException.
+     *
+     * @param context The context of client activity
+     * @param partnerConfig The {@code PartnerConfig} of target resource
+     * @param defaultValue The default value
+     */
+    public boolean getBoolean(
+            @NonNull Context context, PartnerConfig partnerConfig, boolean defaultValue) {
+        if (partnerConfig.getResourceType() != PartnerConfig.ResourceType.BOOLEAN) {
+            throw new IllegalArgumentException("Not a boolean resource");
+        }
+
+        if (mPartnerResourceCache.containsKey(partnerConfig)) {
+            return (boolean) mPartnerResourceCache.get(partnerConfig);
+        }
+
+        boolean result = defaultValue;
+        try {
+            String resourceName = partnerConfig.getResourceName();
+            ResourceEntry resourceEntry = getResourceEntryFromKey(resourceName);
+            if (resourceEntry == null) {
+                Log.w(TAG, "Resource not found: " + resourceName);
+                return defaultValue;
+            }
+            Resources resource = getResourcesByPackageName(context, resourceEntry.getPackageName());
+            result = resource.getBoolean(resourceEntry.getResourceId());
+            mPartnerResourceCache.put(partnerConfig, result);
+        } catch (PackageManager.NameNotFoundException exception) {
+            Log.e(TAG, exception.getMessage());
+        }
+        return result;
+    }
+
+    private void getPartnerConfigBundle(Context context) {
+        if (mResultBundle == null) {
+            try {
+                Uri contentUri =
+                        new Uri.Builder()
+                                .scheme(ContentResolver.SCHEME_CONTENT)
+                                .authority(SUW_AUTHORITY)
+                                .appendPath(SUW_GET_PARTNER_CONFIG_METHOD)
+                                .build();
+                mResultBundle = context.getContentResolver().call(
+                        contentUri,
+                        SUW_GET_PARTNER_CONFIG_METHOD,
+                        /* arg= */ null,
+                        /* extras= */ null);
+                mPartnerResourceCache.clear();
+            } catch (IllegalArgumentException exception) {
+                Log.w(TAG, "Fail to get config from suw provider");
+            }
+        }
+    }
+
+    private Resources getResourcesByPackageName(Context context, String packageName)
+            throws PackageManager.NameNotFoundException {
+        PackageManager manager = context.getPackageManager();
+        return manager.getResourcesForApplication(packageName);
+    }
+
+    private ResourceEntry getResourceEntryFromKey(String resourceName) {
+        if (mResultBundle == null) {
+            return null;
+        }
+        return ResourceEntry.fromBundle(mResultBundle.getBundle(resourceName));
+    }
+
+    private PartnerConfigHelper(Context context) {
+        getPartnerConfigBundle(context);
+    }
+
+
+    @VisibleForTesting
+    static synchronized void resetForTesting() {
+        sInstance = null;
+    }
+}
diff --git a/com/android/car/setupwizardlib/partner/PartnerConfigKey.java b/com/android/car/setupwizardlib/partner/PartnerConfigKey.java
new file mode 100644
index 0000000..de4aa9e
--- /dev/null
+++ b/com/android/car/setupwizardlib/partner/PartnerConfigKey.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 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 com.android.car.setupwizardlib.partner;
+
+import androidx.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.SOURCE)
+@StringDef({
+        PartnerConfigKey.KEY_TOOLBAR_BG_COLOR,
+        PartnerConfigKey.KEY_TOOLBAR_BUTTON_ICON_BACK,
+        PartnerConfigKey.KEY_TOOLBAR_BUTTON_FONT_FAMILY,
+        PartnerConfigKey.KEY_TOOLBAR_BUTTON_PADDING_HORIZONTAL,
+        PartnerConfigKey.KEY_TOOLBAR_BUTTON_PADDING_VERTICAL,
+        PartnerConfigKey.KEY_TOOLBAR_BUTTON_RADIUS,
+        PartnerConfigKey.KEY_TOOLBAR_BUTTON_SPACING,
+        PartnerConfigKey.KEY_TOOLBAR_BUTTON_TEXT_SIZE,
+        PartnerConfigKey.KEY_TOOLBAR_PRIMARY_BUTTON_BG,
+        PartnerConfigKey.KEY_TOOLBAR_PRIMARY_BUTTON_BG_COLOR,
+        PartnerConfigKey.KEY_TOOLBAR_PRIMARY_BUTTON_TEXT_COLOR,
+        PartnerConfigKey.KEY_TOOLBAR_SECONDARY_BUTTON_BG,
+        PartnerConfigKey.KEY_TOOLBAR_SECONDARY_BUTTON_BG_COLOR,
+        PartnerConfigKey.KEY_TOOLBAR_SECONDARY_BUTTON_TEXT_COLOR,
+        PartnerConfigKey.KEY_TOOLBAR_DIVIDER_BG,
+        PartnerConfigKey.KEY_TOOLBAR_DIVIDER_LINE_WEIGHT,
+        PartnerConfigKey.KEY_LOADING_INDICATOR_COLOR,
+        PartnerConfigKey.KEY_LOADING_INDICATOR_LINE_WEIGHT,
+        PartnerConfigKey.KEY_LAYOUT_BG_COLOR
+})
+
+/** Resource names that can be customized by partner overlay APK. */
+public @interface PartnerConfigKey {
+
+    String KEY_TOOLBAR_BG_COLOR = "suw_compat_toolbar_bg_color";
+
+    String KEY_TOOLBAR_BUTTON_ICON_BACK = "suw_compat_toolbar_button_icon_back";
+
+    String KEY_TOOLBAR_BUTTON_FONT_FAMILY = "suw_compat_toolbar_button_font_family";
+
+    String KEY_TOOLBAR_BUTTON_TEXT_SIZE = "suw_compat_toolbar_button_text_size";
+
+    String KEY_TOOLBAR_BUTTON_PADDING_HORIZONTAL = "suw_compat_toolbar_button_padding_horizontal";
+
+    String KEY_TOOLBAR_BUTTON_PADDING_VERTICAL = "suw_compat_toolbar_button_padding_vertical";
+
+    String KEY_TOOLBAR_BUTTON_RADIUS = "suw_compat_toolbar_button_radius";
+
+    String KEY_TOOLBAR_BUTTON_SPACING = "suw_compat_toolbar_button_spacing";
+
+    String KEY_TOOLBAR_PRIMARY_BUTTON_BG = "suw_compat_toolbar_primary_button_bg";
+
+    String KEY_TOOLBAR_PRIMARY_BUTTON_BG_COLOR =
+            "suw_compat_toolbar_primary_button_bg_color";
+
+    String KEY_TOOLBAR_PRIMARY_BUTTON_TEXT_COLOR = "suw_compat_toolbar_primary_button_text_color";
+
+    String KEY_TOOLBAR_SECONDARY_BUTTON_BG = "suw_compat_toolbar_secondary_button_bg";
+
+    String KEY_TOOLBAR_SECONDARY_BUTTON_BG_COLOR = "suw_compat_toolbar_secondary_button_bg_color";
+
+    String KEY_TOOLBAR_SECONDARY_BUTTON_TEXT_COLOR =
+            "suw_compat_toolbar_secondary_button_text_color";
+
+    String KEY_TOOLBAR_DIVIDER_BG = "suw_compat_toolbar_divider_bg";
+
+    String KEY_TOOLBAR_DIVIDER_LINE_WEIGHT = "suw_compat_toolbar_divider_line_weight";
+
+    String KEY_LOADING_INDICATOR_COLOR = "suw_compat_loading_indicator_color";
+
+    String KEY_LOADING_INDICATOR_LINE_WEIGHT = "suw_compat_loading_indicator_line_weight";
+
+    String KEY_LAYOUT_BG_COLOR = "suw_design_layout_bg_color";
+}
diff --git a/com/android/car/setupwizardlib/partner/ResourceEntry.java b/com/android/car/setupwizardlib/partner/ResourceEntry.java
new file mode 100644
index 0000000..6d5d39b
--- /dev/null
+++ b/com/android/car/setupwizardlib/partner/ResourceEntry.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 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 com.android.car.setupwizardlib.partner;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * A potentially cross-package resource entry, which can then be retrieved using {@link
+ * PackageManager#getResourcesForApplication(String)}. This class can also be sent across to other
+ * packages on IPC via the Bundle representation.
+ */
+public final class ResourceEntry {
+    @VisibleForTesting static final String KEY_PACKAGE_NAME = "packageName";
+    @VisibleForTesting static final String KEY_RESOURCE_NAME = "resourceName";
+    @VisibleForTesting static final String KEY_RESOURCE_ID = "resourceId";
+
+    private final String mPackageName;
+    private final String mResourceName;
+    private final int mResourceId;
+
+    /**
+     * Creates a {@code ResourceEntry} object from a provided bundle.
+     *
+     * @param bundle the source bundle needs to have all the information for a {@code ResourceEntry}
+     */
+    public static ResourceEntry fromBundle(@Nullable Bundle bundle) {
+        if (bundle == null
+                || !bundle.containsKey(KEY_PACKAGE_NAME)
+                || !bundle.containsKey(KEY_RESOURCE_NAME)
+                || !bundle.containsKey(KEY_RESOURCE_ID)) {
+            return null;
+        }
+
+        String packageName = bundle.getString(KEY_PACKAGE_NAME);
+        String resourceName = bundle.getString(KEY_RESOURCE_NAME);
+        int resourceId = bundle.getInt(KEY_RESOURCE_ID);
+        return new ResourceEntry(packageName, resourceName, resourceId);
+    }
+
+    public ResourceEntry(String packageName, String resourceName, int resourceId) {
+        mPackageName = packageName;
+        mResourceName = resourceName;
+        mResourceId = resourceId;
+    }
+
+    public String getPackageName() {
+        return this.mPackageName;
+    }
+
+    public String getResourceName() {
+        return this.mResourceName;
+    }
+
+    public int getResourceId() {
+        return this.mResourceId;
+    }
+
+    /**
+     * Returns a bundle representation of this resource entry, which can then be sent over IPC.
+     *
+     * @see #fromBundle(Bundle)
+     */
+    public Bundle toBundle() {
+        Bundle result = new Bundle();
+        result.putString(KEY_PACKAGE_NAME, mPackageName);
+        result.putString(KEY_RESOURCE_NAME, mResourceName);
+        result.putInt(KEY_RESOURCE_ID, mResourceId);
+        return result;
+    }
+}