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.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.text.TextUtils;
28 import android.util.Log;
29 
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 
36 public class TvCustomizationManager {
37     private static final String TAG = "TvCustomizationManager";
38     private static final boolean DEBUG = false;
39 
40     private static final String CATEGORY_TV_CUSTOMIZATION =
41             "com.android.tv.category";
42 
43     /**
44      * Row IDs to share customized actions.
45      * Only rows listed below can have customized action.
46      */
47     public static final String ID_OPTIONS_ROW = "options_row";
48     public static final String ID_PARTNER_ROW = "partner_row";
49 
50     private static final HashMap<String, String> INTENT_CATEGORY_TO_ROW_ID;
51     static {
52         INTENT_CATEGORY_TO_ROW_ID = new HashMap<>();
53         INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".OPTIONS_ROW", ID_OPTIONS_ROW);
54         INTENT_CATEGORY_TO_ROW_ID.put(CATEGORY_TV_CUSTOMIZATION + ".PARTNER_ROW", ID_PARTNER_ROW);
55     }
56 
57     private static final String RES_ID_PARTNER_ROW_TITLE = "partner_row_title";
58 
59     private static final String RES_TYPE_STRING = "string";
60 
61     private final Context mContext;
62     private boolean mInitialized;
63     private String mCustomizationPackage;
64 
65     private String mPartnerRowTitle;
66     private final Map<String, List<CustomAction>> mRowIdToCustomActionsMap = new HashMap<>();
67 
TvCustomizationManager(Context context)68     public TvCustomizationManager(Context context) {
69         mContext = context;
70         mInitialized = false;
71     }
72 
73     /**
74      * Initialize TV customization options.
75      * Run this API only on the main thread.
76      */
initialize()77     public void initialize() {
78         if (mInitialized) {
79             return;
80         }
81         mInitialized = true;
82         buildCustomActions();
83         if (!TextUtils.isEmpty(mCustomizationPackage)) {
84             buildPartnerRow();
85         }
86     }
87 
buildCustomActions()88     private void buildCustomActions() {
89         mCustomizationPackage = null;
90         mRowIdToCustomActionsMap.clear();
91         PackageManager pm = mContext.getPackageManager();
92         for (String intentCategory : INTENT_CATEGORY_TO_ROW_ID.keySet()) {
93             Intent customOptionIntent = new Intent(Intent.ACTION_MAIN);
94             customOptionIntent.addCategory(intentCategory);
95 
96             List<ResolveInfo> activities = pm.queryIntentActivities(customOptionIntent,
97                     PackageManager.GET_RECEIVERS | PackageManager.GET_RESOLVED_FILTER
98                             | PackageManager.GET_META_DATA);
99             for (ResolveInfo info : activities) {
100                 String packageName = info.activityInfo.packageName;
101                 if (TextUtils.isEmpty(mCustomizationPackage)) {
102                     if (DEBUG) Log.d(TAG, "Found TV customization package " + packageName);
103                     if ((info.activityInfo.applicationInfo.flags
104                             & ApplicationInfo.FLAG_SYSTEM) == 0) {
105                         Log.w(TAG, "Only system app can customize TV. Ignoring " + packageName);
106                         continue;
107                     }
108                     mCustomizationPackage = packageName;
109                 } else if (!packageName.equals(mCustomizationPackage)) {
110                     Log.w(TAG, "A customization package " + mCustomizationPackage
111                             + " already exist. Ignoring " + packageName);
112                     continue;
113                 }
114 
115                 int position = info.filter.getPriority();
116                 String title = info.loadLabel(pm).toString();
117                 Drawable drawable = info.loadIcon(pm);
118                 Intent intent = new Intent(Intent.ACTION_MAIN);
119                 intent.addCategory(intentCategory);
120                 intent.setClassName(mCustomizationPackage, info.activityInfo.name);
121 
122                 String rowId = INTENT_CATEGORY_TO_ROW_ID.get(intentCategory);
123                 List<CustomAction> actions = mRowIdToCustomActionsMap.get(rowId);
124                 if (actions == null) {
125                     actions = new ArrayList<>();
126                     mRowIdToCustomActionsMap.put(rowId, actions);
127                 }
128                 actions.add(new CustomAction(position, title, drawable, intent));
129             }
130         }
131         // Sort items by position
132         for (List<CustomAction> actions : mRowIdToCustomActionsMap.values()) {
133             Collections.sort(actions);
134         }
135 
136         if (DEBUG) {
137             Log.d(TAG, "Dumping custom actions");
138             for (String id : mRowIdToCustomActionsMap.keySet()) {
139                 for (CustomAction action : mRowIdToCustomActionsMap.get(id)) {
140                     Log.d(TAG, "Custom row rowId=" + id + " title=" + action.getTitle()
141                         + " class=" + action.getIntent());
142                 }
143             }
144             Log.d(TAG, "Dumping custom actions - end of dump");
145         }
146     }
147 
148     /**
149      * Returns custom actions for given row id.
150      *
151      * Row ID is one of ID_OPTIONS_ROW or ID_PARTNER_ROW.
152      */
getCustomActions(String rowId)153     public List<CustomAction> getCustomActions(String rowId) {
154         return mRowIdToCustomActionsMap.get(rowId);
155     }
156 
buildPartnerRow()157     private void buildPartnerRow() {
158         mPartnerRowTitle = null;
159         Resources res;
160         try {
161             res = mContext.getPackageManager()
162                     .getResourcesForApplication(mCustomizationPackage);
163         } catch (NameNotFoundException e) {
164             Log.w(TAG, "Could not get resources for package " + mCustomizationPackage);
165             return;
166         }
167         int resId = res.getIdentifier(
168                 RES_ID_PARTNER_ROW_TITLE, RES_TYPE_STRING, mCustomizationPackage);
169         if (resId != 0) {
170             mPartnerRowTitle = res.getString(resId);
171         }
172         if (DEBUG) Log.d(TAG, "Partner row title [" + mPartnerRowTitle + "]");
173     }
174 
175     /**
176      * Returns partner row title.
177      */
getPartnerRowTitle()178     public String getPartnerRowTitle() {
179         return mPartnerRowTitle;
180     }
181 }
182