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 17 package com.google.android.setupdesign.util; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.pm.ApplicationInfo; 22 import android.content.pm.PackageManager; 23 import android.content.pm.PackageManager.NameNotFoundException; 24 import android.content.pm.ResolveInfo; 25 import android.content.res.Resources; 26 import android.graphics.drawable.Drawable; 27 import android.os.Build.VERSION; 28 import android.os.Build.VERSION_CODES; 29 import androidx.annotation.AnyRes; 30 import androidx.annotation.ColorRes; 31 import androidx.annotation.DrawableRes; 32 import androidx.annotation.Nullable; 33 import androidx.annotation.StringRes; 34 import androidx.annotation.VisibleForTesting; 35 import android.util.Log; 36 import java.util.List; 37 38 /** 39 * Utilities to discover and interact with partner customizations. An overlay package is one that 40 * registers the broadcast receiver for {@code com.android.setupwizard.action.PARTNER_CUSTOMIZATION} 41 * in its manifest. There can only be one customization APK on a device, and it must be bundled with 42 * the system. 43 * 44 * <p>Derived from {@code com.android.launcher3/Partner.java} 45 */ 46 public class Partner { 47 48 private static final String TAG = "(setupdesign) Partner"; 49 50 /** Marker action used to discover partner. */ 51 private static final String ACTION_PARTNER_CUSTOMIZATION = 52 "com.android.setupwizard.action.PARTNER_CUSTOMIZATION"; 53 54 private static boolean searched = false; 55 @Nullable private static Partner partner; 56 57 /** 58 * Gets a drawable from partner overlay, or if not available, the drawable from the original 59 * context. 60 * 61 * @see #getResourceEntry(android.content.Context, int) 62 */ 63 public static Drawable getDrawable(Context context, @DrawableRes int id) { 64 final ResourceEntry entry = getResourceEntry(context, id); 65 return entry.resources.getDrawable(entry.id); 66 } 67 68 /** 69 * Gets a string from partner overlay, or if not available, the string from the original context. 70 * 71 * @see #getResourceEntry(android.content.Context, int) 72 */ 73 public static String getString(Context context, @StringRes int id) { 74 final ResourceEntry entry = getResourceEntry(context, id); 75 return entry.resources.getString(entry.id); 76 } 77 78 /** 79 * Gets a color from partner overlay, or if not available, the color from the original context. 80 */ 81 public static int getColor(Context context, @ColorRes int id) { 82 final ResourceEntry resourceEntry = getResourceEntry(context, id); 83 return resourceEntry.resources.getColor(resourceEntry.id); 84 } 85 86 /** 87 * Gets a CharSequence from partner overlay, or if not available, the text from the original 88 * context. 89 */ 90 public static CharSequence getText(Context context, @StringRes int id) { 91 final ResourceEntry entry = getResourceEntry(context, id); 92 return entry.resources.getText(entry.id); 93 } 94 95 /** 96 * Finds an entry of resource in the overlay package provided by partners. It will first look for 97 * the resource in the overlay package, and if not available, will return the one in the original 98 * context. 99 * 100 * @return a ResourceEntry in the partner overlay's resources, if one is defined. Otherwise the 101 * resources from the original context is returned. Clients can then get the resource by 102 * {@code entry.resources.getString(entry.id)}, or other methods available in {@link 103 * android.content.res.Resources}. 104 */ 105 public static ResourceEntry getResourceEntry(Context context, @AnyRes int id) { 106 final Partner partner = Partner.get(context); 107 if (partner != null) { 108 final Resources ourResources = context.getResources(); 109 final String name = ourResources.getResourceEntryName(id); 110 final String type = ourResources.getResourceTypeName(id); 111 final int partnerId = partner.getIdentifier(name, type); 112 if (partnerId != 0) { 113 return new ResourceEntry(partner.getPackageName(), partner.resources, partnerId, true); 114 } 115 } 116 return new ResourceEntry(context.getPackageName(), context.getResources(), id, false); 117 } 118 119 public static class ResourceEntry { 120 public String packageName; 121 public Resources resources; 122 public int id; 123 public boolean isOverlay; 124 125 ResourceEntry(String packageName, Resources resources, int id, boolean isOverlay) { 126 this.packageName = packageName; 127 this.resources = resources; 128 this.id = id; 129 this.isOverlay = isOverlay; 130 } 131 } 132 133 /** 134 * Finds and returns partner details, or {@code null} if none exists. A partner package is marked 135 * by a broadcast receiver declared in the manifest that handles the {@code 136 * com.android.setupwizard.action.PARTNER_CUSTOMIZATION} intent action. The overlay package must 137 * also be a system package. 138 */ 139 public static synchronized Partner get(Context context) { 140 if (!searched) { 141 PackageManager pm = context.getPackageManager(); 142 final Intent intent = new Intent(ACTION_PARTNER_CUSTOMIZATION); 143 List<ResolveInfo> receivers; 144 if (VERSION.SDK_INT >= VERSION_CODES.N) { 145 receivers = 146 pm.queryBroadcastReceivers( 147 intent, 148 PackageManager.MATCH_SYSTEM_ONLY 149 | PackageManager.MATCH_DIRECT_BOOT_AWARE 150 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 151 | PackageManager.MATCH_DISABLED_COMPONENTS); 152 } else { 153 // On versions before N, direct boot doesn't exist. And the MATCH_SYSTEM_ONLY flag 154 // doesn't exist so we filter for system apps in code below. 155 receivers = pm.queryBroadcastReceivers(intent, PackageManager.GET_DISABLED_COMPONENTS); 156 } 157 158 for (ResolveInfo info : receivers) { 159 if (info.activityInfo == null) { 160 continue; 161 } 162 final ApplicationInfo appInfo = info.activityInfo.applicationInfo; 163 if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 164 try { 165 final Resources res = pm.getResourcesForApplication(appInfo); 166 partner = new Partner(appInfo.packageName, res); 167 break; 168 } catch (NameNotFoundException e) { 169 Log.w(TAG, "Failed to find resources for " + appInfo.packageName); 170 } 171 } 172 } 173 searched = true; 174 } 175 return partner; 176 } 177 178 @VisibleForTesting 179 public static synchronized void resetForTesting() { 180 searched = false; 181 partner = null; 182 } 183 184 private final String packageName; 185 private final Resources resources; 186 187 private Partner(String packageName, Resources res) { 188 this.packageName = packageName; 189 resources = res; 190 } 191 192 public String getPackageName() { 193 return packageName; 194 } 195 196 public Resources getResources() { 197 return resources; 198 } 199 200 public int getIdentifier(String name, String defType) { 201 return resources.getIdentifier(name, defType, packageName); 202 } 203 } 204