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.tv.customization;
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.text.TextUtils;
28import android.util.Log;
29
30import java.util.ArrayList;
31import java.util.Collections;
32import java.util.HashMap;
33import java.util.List;
34import java.util.Map;
35
36public 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
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     */
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
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     */
153    public List<CustomAction> getCustomActions(String rowId) {
154        return mRowIdToCustomActionsMap.get(rowId);
155    }
156
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     */
178    public String getPartnerRowTitle() {
179        return mPartnerRowTitle;
180    }
181}
182