Home | History | Annotate | Download | only in customization
      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