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