1/*
2 * Copyright (C) 2015 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 com.android.setupwizardlib.util;
18
19import android.content.Context;
20import android.content.Intent;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageManager;
23import android.content.pm.PackageManager.NameNotFoundException;
24import android.content.pm.ResolveInfo;
25import android.content.res.Resources;
26import android.graphics.drawable.Drawable;
27import android.util.Log;
28
29import com.android.setupwizardlib.annotations.VisibleForTesting;
30
31/**
32 * Utilities to discover and interact with partner customizations. There can only be one set of
33 * customizations on a device, and it must be bundled with the system.
34 *
35 * Derived from com.android.launcher3/Partner.java
36 */
37public class Partner {
38    private static final String TAG = "(SUW) Partner";
39
40    /** Marker action used to discover partner */
41    private static final String
42            ACTION_PARTNER_CUSTOMIZATION = "com.android.setupwizard.action.PARTNER_CUSTOMIZATION";
43
44    private static boolean sSearched = false;
45    private static Partner sPartner;
46
47    /**
48     * Convenience to get a drawable from partner overlay, or if not available, the drawable from
49     * the original context.
50     *
51     * @see #getResourceEntry(android.content.Context, int)
52     */
53    public static Drawable getDrawable(Context context, int id) {
54        final ResourceEntry entry = getResourceEntry(context, id);
55        return entry.resources.getDrawable(entry.id);
56    }
57
58    /**
59     * Convenience to get a string from partner overlay, or if not available, the string from the
60     * original context.
61     *
62     * @see #getResourceEntry(android.content.Context, int)
63     */
64    public static String getString(Context context, int id) {
65        final ResourceEntry entry = getResourceEntry(context, id);
66        return entry.resources.getString(entry.id);
67    }
68
69    /**
70     * Find an entry of resource in the overlay package provided by partners. It will first look for
71     * the resource in the overlay package, and if not available, will return the one in the
72     * original context.
73     *
74     * @return a ResourceEntry in the partner overlay's resources, if one is defined. Otherwise the
75     * resources from the original context is returned. Clients can then get the resource by
76     * {@code entry.resources.getString(entry.id)}, or other methods available in
77     * {@link android.content.res.Resources}.
78     */
79    public static ResourceEntry getResourceEntry(Context context, int id) {
80        final Partner partner = Partner.get(context);
81        if (partner != null) {
82            final Resources ourResources = context.getResources();
83            final String name = ourResources.getResourceEntryName(id);
84            final String type = ourResources.getResourceTypeName(id);
85            final int partnerId = partner.getIdentifier(name, type);
86            if (partnerId != 0) {
87                return new ResourceEntry(partner.mResources, partnerId, true);
88            }
89        }
90        return new ResourceEntry(context.getResources(), id, false);
91    }
92
93    public static class ResourceEntry {
94        public Resources resources;
95        public int id;
96        public boolean isOverlay;
97
98        ResourceEntry(Resources resources, int id, boolean isOverlay) {
99            this.resources = resources;
100            this.id = id;
101            this.isOverlay = isOverlay;
102        }
103    }
104
105    /**
106     * Find and return partner details, or {@code null} if none exists. A partner package is marked
107     * by a broadcast receiver declared in the manifest that handles the
108     * com.android.setupwizard.action.PARTNER_CUSTOMIZATION intent action. The overlay package must
109     * also be a system package.
110     */
111    public static synchronized Partner get(Context context) {
112        if (!sSearched) {
113            PackageManager pm = context.getPackageManager();
114            final Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION);
115            for (ResolveInfo info : pm.queryBroadcastReceivers(intent, 0)) {
116                if (info.activityInfo == null) {
117                    continue;
118                }
119                final ApplicationInfo appInfo = info.activityInfo.applicationInfo;
120                if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
121                    try {
122                        final Resources res = pm.getResourcesForApplication(appInfo);
123                        sPartner = new Partner(appInfo.packageName, res);
124                        break;
125                    } catch (NameNotFoundException e) {
126                        Log.w(TAG, "Failed to find resources for " + appInfo.packageName);
127                    }
128                }
129            }
130            sSearched = true;
131        }
132        return sPartner;
133    }
134
135    @VisibleForTesting
136    public static synchronized void resetForTesting() {
137        sSearched = false;
138        sPartner = null;
139    }
140
141    private final String mPackageName;
142    private final Resources mResources;
143
144    private Partner(String packageName, Resources res) {
145        mPackageName = packageName;
146        mResources = res;
147    }
148
149    public String getPackageName() {
150        return mPackageName;
151    }
152
153    public Resources getResources() {
154        return mResources;
155    }
156
157    public int getIdentifier(String name, String defType) {
158        return mResources.getIdentifier(name, defType, mPackageName);
159    }
160}
161