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.android.tv.customization; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.pm.PackageInfo; 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.support.annotation.IntDef; 28 import android.text.TextUtils; 29 import android.util.Log; 30 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.RetentionPolicy; 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Map; 38 39 public class TvCustomizationManager { 40 private static final String TAG = "TvCustomizationManager"; 41 private static final boolean DEBUG = false; 42 43 private static final String[] CUSTOMIZE_PERMISSIONS = { 44 "com.android.tv.permission.CUSTOMIZE_TV_APP" 45 }; 46 47 private static final String CATEGORY_TV_CUSTOMIZATION = 48 "com.android.tv.category"; 49 50 /** 51 * Row IDs to share customized actions. 52 * Only rows listed below can have customized action. 53 */ 54 public static final String ID_OPTIONS_ROW = "options_row"; 55 public static final String ID_PARTNER_ROW = "partner_row"; 56 57 @IntDef({TRICKPLAY_MODE_ENABLED, TRICKPLAY_MODE_DISABLED, TRICKPLAY_MODE_USE_EXTERNAL_STORAGE}) 58 @Retention(RetentionPolicy.SOURCE) 59 public @interface TRICKPLAY_MODE {} 60 public static final int TRICKPLAY_MODE_ENABLED = 0; 61 public static final int TRICKPLAY_MODE_DISABLED = 1; 62 public static final int TRICKPLAY_MODE_USE_EXTERNAL_STORAGE = 2; 63 64 private static final String[] TRICKPLAY_MODE_STRINGS = { 65 "enabled", 66 "disabled", 67 "use_external_storage_only" 68 }; 69 70 private static final HashMap<String, String> INTENT_CATEGORY_TO_ROW_ID; 71 static { 72 INTENT_CATEGORY_TO_ROW_ID = new HashMap<>(); 73 INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".OPTIONS_ROW", ID_OPTIONS_ROW); 74 INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".PARTNER_ROW", ID_PARTNER_ROW); 75 } 76 77 private static final String RES_ID_PARTNER_ROW_TITLE = "partner_row_title"; 78 private static final String RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER = 79 "has_linux_dvb_built_in_tuner"; 80 private static final String RES_ID_TRICKPLAY_MODE = "trickplay_mode"; 81 82 private static final String RES_TYPE_STRING = "string"; 83 private static final String RES_TYPE_BOOLEAN = "bool"; 84 85 private static String sCustomizationPackage; 86 private static Boolean sHasLinuxDvbBuiltInTuner; 87 private static @TRICKPLAY_MODE Integer sTrickplayMode; 88 89 private final Context mContext; 90 private boolean mInitialized; 91 92 private String mPartnerRowTitle; 93 private final Map<String, List<CustomAction>> mRowIdToCustomActionsMap = new HashMap<>(); 94 95 public TvCustomizationManager(Context context) { 96 mContext = context; 97 mInitialized = false; 98 } 99 100 /** 101 * Returns {@code true} if there's a customization package installed and it specifies built-in 102 * tuner devices are available. The built-in tuner should support DVB API to be recognized by 103 * Live TV. 104 */ 105 public static boolean hasLinuxDvbBuiltInTuner(Context context) { 106 if (sHasLinuxDvbBuiltInTuner == null) { 107 if (TextUtils.isEmpty(getCustomizationPackageName(context))) { 108 sHasLinuxDvbBuiltInTuner = false; 109 } else { 110 try { 111 Resources res = context.getPackageManager() 112 .getResourcesForApplication(sCustomizationPackage); 113 int resId = res.getIdentifier(RES_ID_HAS_LINUX_DVB_BUILT_IN_TUNER, 114 RES_TYPE_BOOLEAN, sCustomizationPackage); 115 sHasLinuxDvbBuiltInTuner = resId != 0 && res.getBoolean(resId); 116 } catch (NameNotFoundException e) { 117 sHasLinuxDvbBuiltInTuner = false; 118 } 119 } 120 } 121 return sHasLinuxDvbBuiltInTuner; 122 } 123 124 public static @TRICKPLAY_MODE int getTrickplayMode(Context context) { 125 if (sTrickplayMode == null) { 126 if (TextUtils.isEmpty(getCustomizationPackageName(context))) { 127 sTrickplayMode = TRICKPLAY_MODE_ENABLED; 128 } else { 129 try { 130 String customization = null; 131 Resources res = context.getPackageManager() 132 .getResourcesForApplication(sCustomizationPackage); 133 int resId = res.getIdentifier(RES_ID_TRICKPLAY_MODE, 134 RES_TYPE_STRING, sCustomizationPackage); 135 customization = resId == 0 ? null : res.getString(resId); 136 sTrickplayMode = TRICKPLAY_MODE_ENABLED; 137 if (customization != null) { 138 for (int i = 0; i < TRICKPLAY_MODE_STRINGS.length; ++i) { 139 if (TRICKPLAY_MODE_STRINGS[i].equalsIgnoreCase(customization)) { 140 sTrickplayMode = i; 141 break; 142 } 143 } 144 } 145 } catch (NameNotFoundException e) { 146 sTrickplayMode = TRICKPLAY_MODE_ENABLED; 147 } 148 } 149 } 150 return sTrickplayMode; 151 } 152 153 private static String getCustomizationPackageName(Context context) { 154 if (sCustomizationPackage == null) { 155 List<PackageInfo> packageInfos = context.getPackageManager() 156 .getPackagesHoldingPermissions(CUSTOMIZE_PERMISSIONS, 0); 157 sCustomizationPackage = packageInfos.size() == 0 ? "" : packageInfos.get(0).packageName; 158 } 159 return sCustomizationPackage; 160 } 161 162 /** 163 * Initialize TV customization options. 164 * Run this API only on the main thread. 165 */ 166 public void initialize() { 167 if (mInitialized) { 168 return; 169 } 170 mInitialized = true; 171 if (!TextUtils.isEmpty(getCustomizationPackageName(mContext))) { 172 buildCustomActions(); 173 buildPartnerRow(); 174 } 175 } 176 177 private void buildCustomActions() { 178 mRowIdToCustomActionsMap.clear(); 179 PackageManager pm = mContext.getPackageManager(); 180 for (String intentCategory : INTENT_CATEGORY_TO_ROW_ID.keySet()) { 181 Intent customOptionIntent = new Intent(Intent.ACTION_MAIN); 182 customOptionIntent.addCategory(intentCategory); 183 184 List<ResolveInfo> activities = pm.queryIntentActivities(customOptionIntent, 185 PackageManager.GET_RECEIVERS | PackageManager.GET_RESOLVED_FILTER 186 | PackageManager.GET_META_DATA); 187 for (ResolveInfo info : activities) { 188 String packageName = info.activityInfo.packageName; 189 if (!TextUtils.equals(packageName, sCustomizationPackage)) { 190 Log.w(TAG, "A customization package " + sCustomizationPackage 191 + " already exist. Ignoring " + packageName); 192 continue; 193 } 194 195 int position = info.filter.getPriority(); 196 String title = info.loadLabel(pm).toString(); 197 Drawable drawable = info.loadIcon(pm); 198 Intent intent = new Intent(Intent.ACTION_MAIN); 199 intent.addCategory(intentCategory); 200 intent.setClassName(sCustomizationPackage, info.activityInfo.name); 201 202 String rowId = INTENT_CATEGORY_TO_ROW_ID.get(intentCategory); 203 List<CustomAction> actions = mRowIdToCustomActionsMap.get(rowId); 204 if (actions == null) { 205 actions = new ArrayList<>(); 206 mRowIdToCustomActionsMap.put(rowId, actions); 207 } 208 actions.add(new CustomAction(position, title, drawable, intent)); 209 } 210 } 211 // Sort items by position 212 for (List<CustomAction> actions : mRowIdToCustomActionsMap.values()) { 213 Collections.sort(actions); 214 } 215 216 if (DEBUG) { 217 Log.d(TAG, "Dumping custom actions"); 218 for (String id : mRowIdToCustomActionsMap.keySet()) { 219 for (CustomAction action : mRowIdToCustomActionsMap.get(id)) { 220 Log.d(TAG, "Custom row rowId=" + id + " title=" + action.getTitle() 221 + " class=" + action.getIntent()); 222 } 223 } 224 Log.d(TAG, "Dumping custom actions - end of dump"); 225 } 226 } 227 228 /** 229 * Returns custom actions for given row id. 230 * 231 * Row ID is one of ID_OPTIONS_ROW or ID_PARTNER_ROW. 232 */ 233 public List<CustomAction> getCustomActions(String rowId) { 234 return mRowIdToCustomActionsMap.get(rowId); 235 } 236 237 private void buildPartnerRow() { 238 mPartnerRowTitle = null; 239 Resources res; 240 try { 241 res = mContext.getPackageManager() 242 .getResourcesForApplication(sCustomizationPackage); 243 } catch (NameNotFoundException e) { 244 Log.w(TAG, "Could not get resources for package " + sCustomizationPackage); 245 return; 246 } 247 int resId = res.getIdentifier( 248 RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, sCustomizationPackage); 249 if (resId != 0) { 250 mPartnerRowTitle = res.getString(resId); 251 } 252 if (DEBUG) Log.d(TAG, "Partner row title [" + mPartnerRowTitle + "]"); 253 } 254 255 /** 256 * Returns partner row title. 257 */ 258 public String getPartnerRowTitle() { 259 return mPartnerRowTitle; 260 } 261 } 262