Home | History | Annotate | Download | only in system
      1 /*
      2  * Copyright (C) 2014 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.settings.system;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.res.Resources;
     22 import android.media.tv.TvInputInfo;
     23 import android.media.tv.TvInputInfo.TvInputSettings;
     24 import android.media.tv.TvInputManager;
     25 import android.os.Bundle;
     26 import android.os.UserHandle;
     27 import android.provider.Settings.Global;
     28 import android.text.TextUtils;
     29 import android.util.Log;
     30 import android.util.Pair;
     31 
     32 import com.android.tv.settings.dialog.SettingsLayoutActivity;
     33 import com.android.tv.settings.dialog.Layout;
     34 import com.android.tv.settings.dialog.Layout.Header;
     35 import com.android.tv.settings.dialog.Layout.Action;
     36 import com.android.tv.settings.dialog.Layout.Status;
     37 import com.android.tv.settings.dialog.Layout.Static;
     38 import com.android.tv.settings.dialog.Layout.StringGetter;
     39 import com.android.tv.settings.dialog.Layout.LayoutGetter;
     40 
     41 import com.android.tv.settings.R;
     42 
     43 import java.util.ArrayList;
     44 import java.util.Collections;
     45 import java.util.Comparator;
     46 import java.util.HashMap;
     47 import java.util.LinkedHashMap;
     48 import java.util.Map;
     49 import java.util.Set;
     50 
     51 /**
     52  * Activity to control TV input settings.
     53  */
     54 public class InputsActivity extends SettingsLayoutActivity {
     55     private static final String TAG = "InputsActivity";
     56     private static final boolean DEBUG = false;
     57 
     58     private static final int ACTION_EDIT_LABEL = 0;
     59     private static final int ACTION_CUSTOM_LABEL = 1;
     60     private static final int ACTION_HIDE = 2;
     61     private static final int ACTION_HDMI_CONTROL = 3;
     62     private static final int ACTION_DEVICE_AUTO_OFF = 4;
     63     private static final int ACTION_TV_AUTO_ON = 5;
     64 
     65     private static final String KEY_ID = "id";
     66     private static final String KEY_LABEL = "label";
     67     private static final String KEY_ON = "on";
     68 
     69     private static final int REQUEST_CODE_CUSTOM_LABEL = 0;
     70 
     71     private static final int DISABLED = 0;
     72     private static final int ENABLED = 1;
     73 
     74     private static final int PREDEFINED_LABEL_RES_IDS[] = {
     75         R.string.inputs_blu_ray,
     76         R.string.inputs_cable,
     77         R.string.inputs_dvd,
     78         R.string.inputs_game,
     79         R.string.inputs_aux
     80     };
     81 
     82     private static final LinkedHashMap<Integer, Integer> STATE_STRING_ID_MAP =
     83             new LinkedHashMap<Integer, Integer>() {{
     84                 put(TvInputManager.INPUT_STATE_CONNECTED,
     85                         R.plurals.inputs_header_connected_input);
     86                 put(TvInputManager.INPUT_STATE_CONNECTED_STANDBY,
     87                         R.plurals.inputs_header_standby_input);
     88                 put(TvInputManager.INPUT_STATE_DISCONNECTED,
     89                         R.plurals.inputs_header_disconnected_input);
     90             }};
     91 
     92     private TvInputManager mTvInputManager;
     93     private Resources mRes;
     94     private Map<String, String> mCustomLabels;
     95     private Set<String> mHiddenIds;
     96 
     97     @Override
     98     public void onCreate(Bundle savedInstanceState) {
     99         mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
    100         mRes = getResources();
    101         mCustomLabels = TvInputSettings.getCustomLabels(this, UserHandle.USER_OWNER);
    102         mHiddenIds = TvInputSettings.getHiddenTvInputIds(this, UserHandle.USER_OWNER);
    103         super.onCreate(savedInstanceState);
    104     }
    105 
    106     LayoutGetter mInputListLayout = new LayoutGetter() {
    107         @Override
    108         public Layout get() {
    109             return getExternalTvInputListLayout();
    110         }
    111     };
    112 
    113     LayoutGetter mCecSettingsLayout = new LayoutGetter() {
    114         @Override
    115         public Layout get() {
    116             boolean hdmiControl = readCecOption(getCecOptionKey(ACTION_HDMI_CONTROL));
    117             boolean deviceAutoOff = readCecOption(getCecOptionKey(ACTION_DEVICE_AUTO_OFF));
    118             boolean tvAutoOn = readCecOption(getCecOptionKey(ACTION_TV_AUTO_ON));
    119 
    120             return
    121                 new Layout()
    122                     .add(new Header.Builder(mRes)
    123                             .title(R.string.inputs_hdmi_control)
    124                             .description(getOnOffResId(hdmiControl))
    125                             .detailedDescription(R.string.inputs_hdmi_control_desc)
    126                             .build()
    127                         .add(getOnOffLayout(
    128                                 R.string.inputs_hdmi_control,
    129                                 R.string.inputs_hdmi_control_desc,
    130                                 ACTION_HDMI_CONTROL,
    131                                 null,
    132                                 hdmiControl)))
    133                     .add(new Header.Builder(mRes)
    134                             .title(R.string.inputs_device_auto_off)
    135                             .description(getOnOffResId(deviceAutoOff))
    136                             .detailedDescription(R.string.inputs_device_auto_off_desc)
    137                             .build()
    138                         .add(getOnOffLayout(
    139                                 R.string.inputs_device_auto_off,
    140                                 R.string.inputs_device_auto_off_desc,
    141                                 ACTION_DEVICE_AUTO_OFF,
    142                                 null,
    143                                 deviceAutoOff)))
    144                     .add(new Header.Builder(mRes)
    145                             .title(R.string.inputs_tv_auto_on)
    146                             .description(getOnOffResId(tvAutoOn))
    147                             .detailedDescription(R.string.inputs_tv_auto_on_desc)
    148                             .build()
    149                         .add(getOnOffLayout(
    150                                 R.string.inputs_tv_auto_on,
    151                                 R.string.inputs_tv_auto_on_desc,
    152                                 ACTION_TV_AUTO_ON,
    153                                 null,
    154                                 tvAutoOn)));
    155         }
    156     };
    157 
    158     @Override
    159     public Layout createLayout() {
    160         return
    161             new Layout()
    162                 .breadcrumb(getString(R.string.header_category_preferences))
    163                 .add(new Header.Builder(mRes)
    164                         .icon(R.drawable.ic_settings_inputs)
    165                         .title(R.string.inputs_inputs)
    166                         .build()
    167                     .add(mInputListLayout)
    168                     .add(new Static.Builder(mRes)
    169                             .title(R.string.inputs_header_cec)
    170                             .build())
    171                     .add(new Header.Builder(mRes)
    172                              .title(R.string.inputs_cec_settings)
    173                              .build()
    174                         .add(mCecSettingsLayout))
    175                     );
    176     }
    177 
    178     private static Bundle createData(TvInputInfo info) {
    179         Bundle data = new Bundle();
    180         data.putString(KEY_ID, info.getId());
    181         return data;
    182     }
    183 
    184     private static Bundle createData(TvInputInfo info, String label) {
    185         Bundle data = createData(info);
    186         data.putString(KEY_LABEL, label);
    187         return data;
    188     }
    189 
    190     private static int getOnOffResId(boolean enabled) {
    191         return enabled ? R.string.on : R.string.off;
    192     }
    193 
    194     private LayoutGetter getOnOffLayout(final int titleResId, final int descResId,
    195             final int action, final Bundle data, final boolean checked) {
    196         return new LayoutGetter() {
    197             @Override
    198             public Layout get() {
    199                 Bundle on = (data == null) ? new Bundle() : new Bundle(data);
    200                 on.putBoolean(KEY_ON, true);
    201                 Bundle off = (data == null) ? new Bundle() : new Bundle(data);
    202                 off.putBoolean(KEY_ON, false);
    203 
    204                 Layout layout = new Layout()
    205                             .add(new Action.Builder(mRes, action)
    206                                     .title(R.string.on)
    207                                     .data(on)
    208                                     .checked(checked == true)
    209                                     .build())
    210                             .add(new Action.Builder(mRes, action)
    211                                     .title(R.string.off)
    212                                     .data(off)
    213                                     .checked(checked == false)
    214                                     .build());
    215 
    216                 return layout;
    217             }
    218         };
    219     }
    220 
    221     private LayoutGetter getEditLabelLayout(final TvInputInfo info) {
    222         return new LayoutGetter() {
    223             @Override
    224             public Layout get() {
    225                 String defaultLabel = info.loadLabel(InputsActivity.this).toString();
    226                 String customLabel = mCustomLabels.get(info.getId());
    227                 boolean isHidden = mHiddenIds.contains(info.getId());
    228                 boolean isChecked = false;
    229 
    230                 // Add default.
    231                 boolean isDefault = !isHidden && TextUtils.isEmpty(customLabel);
    232                 Layout layout = new Layout()
    233                     .add(new Action.Builder(mRes, ACTION_EDIT_LABEL)
    234                         .title(defaultLabel)
    235                         .data(createData(info, null))
    236                         .checked(isDefault)
    237                         .build());
    238                 isChecked |= isDefault;
    239 
    240                 // Add pre-defined labels.
    241                 for (int i = 0; i < PREDEFINED_LABEL_RES_IDS.length; i++) {
    242                     String name = getString(PREDEFINED_LABEL_RES_IDS[i]);
    243                     boolean checked = !isHidden && name.equals(customLabel);
    244                     layout.add(new Action.Builder(mRes, ACTION_EDIT_LABEL)
    245                               .title(name)
    246                               .data(createData(info, name))
    247                               .checked(checked)
    248                               .build());
    249                     isChecked |= checked;
    250                 }
    251 
    252                 // Add hidden.
    253                 layout.add(new Action.Builder(mRes, ACTION_HIDE)
    254                           .title(R.string.inputs_hide)
    255                           .description(R.string.inputs_hide_desc)
    256                           .data(createData(info))
    257                           .checked(isHidden)
    258                           .build());
    259                 isChecked |= isHidden;
    260 
    261                 // Add custom Label.
    262                 String label =  (isChecked) ? defaultLabel : customLabel;
    263                 layout.add(new Action.Builder(mRes, ACTION_CUSTOM_LABEL)
    264                           .title(R.string.inputs_custom_name)
    265                           .data(createData(info, label))
    266                           .description(label)
    267                           .checked(!isChecked)
    268                           .build());
    269 
    270                 return layout;
    271             }
    272         };
    273     }
    274 
    275     private Layout getExternalTvInputListLayout() {
    276         HashMap<Integer, ArrayList<Pair<String, TvInputInfo>>> externalInputs =
    277                 new HashMap<Integer, ArrayList<Pair<String, TvInputInfo>>>();
    278         for (TvInputInfo info : mTvInputManager.getTvInputList()) {
    279             if (info.getType() != TvInputInfo.TYPE_TUNER &&
    280                     TextUtils.isEmpty(info.getParentId())) {
    281                 int state;
    282                 try {
    283                     state = mTvInputManager.getInputState(info.getId());
    284                 } catch (IllegalArgumentException e) {
    285                     // Input is gone while iterating. Ignore.
    286                     continue;
    287                 }
    288 
    289                 ArrayList list = externalInputs.get(state);
    290                 if (list == null) {
    291                     list = new ArrayList<Pair<String, TvInputInfo>>();
    292                     externalInputs.put(state, list);
    293                 }
    294                 // Cache label because loadLabel does binder call internally
    295                 // and it would be the sort key.
    296                 list.add(Pair.create(info.loadLabel(this).toString(), info));
    297             }
    298         }
    299 
    300         for (ArrayList<Pair<String, TvInputInfo>> list : externalInputs.values()) {
    301             Collections.sort(list, new Comparator<Pair<String, TvInputInfo>>() {
    302                 @Override
    303                 public int compare(Pair<String, TvInputInfo> a, Pair<String, TvInputInfo> b) {
    304                     return a.first.compareTo(b.first);
    305                 }
    306             });
    307         }
    308 
    309         Layout layout = new Layout();
    310         for (LinkedHashMap.Entry<Integer, Integer> state : STATE_STRING_ID_MAP.entrySet()) {
    311             ArrayList<Pair<String, TvInputInfo>> list = externalInputs.get(state.getKey());
    312             if (list != null && list.size() > 0) {
    313                 String header = mRes.getQuantityString(state.getValue(), list.size());
    314                 layout.add(new Static.Builder(mRes)
    315                           .title(header)
    316                           .build());
    317                 for (Pair<String, TvInputInfo> input : list) {
    318                     String customLabel;
    319                     if (mHiddenIds.contains(input.second.getId())) {
    320                         customLabel = getString(R.string.inputs_hide);
    321                     } else {
    322                         customLabel = mCustomLabels.get(input.second.getId());
    323                         if (TextUtils.isEmpty(customLabel)) {
    324                             customLabel = input.second.loadLabel(this).toString();
    325                         }
    326                     }
    327                     layout.add(new Header.Builder(mRes)
    328                                   .title(input.first)
    329                                   .description(customLabel)
    330                                   .build()
    331                               .add(getEditLabelLayout(input.second)));
    332                 }
    333             }
    334         }
    335 
    336         return layout;
    337     }
    338 
    339     @Override
    340     public void onActionClicked(Action action) {
    341         switch (action.getId()) {
    342             case ACTION_EDIT_LABEL:
    343                 handleEditLabel(action);
    344                 goBackToTitle(getString(R.string.inputs_inputs));
    345                 break;
    346             case ACTION_CUSTOM_LABEL:
    347                 displayCustomLabelActivity(action.getData());
    348                 goBackToTitle(getString(R.string.inputs_inputs));
    349                 break;
    350             case ACTION_HIDE:
    351                 handleHide(action);
    352                 goBackToTitle(getString(R.string.inputs_inputs));
    353                 break;
    354             case ACTION_HDMI_CONTROL:
    355             case ACTION_DEVICE_AUTO_OFF:
    356             case ACTION_TV_AUTO_ON:
    357                 handleCecOption(action);
    358                 goBackToTitle(getString(R.string.inputs_cec_settings));
    359                 break;
    360         }
    361     }
    362 
    363     private void handleEditLabel(Action action) {
    364         String id = action.getData().getString(KEY_ID);
    365         String name = action.getData().getString(KEY_LABEL);
    366         saveCustomLabel(id, name);
    367     }
    368 
    369     private void handleHide(Action action) {
    370         handleHide(action.getData().getString(KEY_ID), true);
    371     }
    372 
    373     private void handleHide(String inputId, boolean hide) {
    374         if (DEBUG) Log.d(TAG, "Hide " + inputId + ": " + hide);
    375 
    376         boolean changed = false;
    377         if (hide) {
    378             if (!mHiddenIds.contains(inputId)) {
    379                 mHiddenIds.add(inputId);
    380                 changed = true;
    381             }
    382         } else {
    383             if (mHiddenIds.contains(inputId)) {
    384                 mHiddenIds.remove(inputId);
    385                 changed = true;
    386             }
    387         }
    388         if (changed) {
    389             TvInputSettings.putHiddenTvInputs(this, mHiddenIds, UserHandle.USER_OWNER);
    390         }
    391     }
    392 
    393     private void handleCecOption(Action action) {
    394         String key = getCecOptionKey(action.getId());
    395         boolean enabled = action.getData().getBoolean(KEY_ON);
    396         writeCecOption(key, enabled);
    397     }
    398 
    399     private void saveCustomLabel(String inputId, String label) {
    400         if (DEBUG) Log.d(TAG, "Setting " + inputId + " => " + label);
    401 
    402         if (!TextUtils.isEmpty(label)) {
    403             mCustomLabels.put(inputId, label);
    404         } else {
    405             mCustomLabels.remove(inputId);
    406         }
    407 
    408         TvInputSettings.putCustomLabels(this, mCustomLabels, UserHandle.USER_OWNER);
    409         handleHide(inputId, false);
    410     }
    411 
    412     private void displayCustomLabelActivity(Bundle data) {
    413         String id = data.getString(KEY_ID);
    414         Intent intent = new Intent(this, InputsCustomLabelActivity.class);
    415         intent.putExtra(InputsCustomLabelActivity.KEY_ID, data.getString(KEY_ID));
    416         intent.putExtra(InputsCustomLabelActivity.KEY_LABEL,
    417                 data.getString(KEY_LABEL));
    418         startActivityForResult(intent, REQUEST_CODE_CUSTOM_LABEL);
    419     }
    420 
    421     @Override
    422     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    423         if (requestCode == REQUEST_CODE_CUSTOM_LABEL) {
    424             if (resultCode == InputsCustomLabelActivity.RESULT_OK) {
    425                 String inputId = data.getStringExtra(InputsCustomLabelActivity.KEY_ID);
    426                 String label = data.getStringExtra(InputsCustomLabelActivity.KEY_LABEL);
    427                 saveCustomLabel(inputId, label);
    428                 goBackToTitle(getString(R.string.inputs_inputs));
    429             }
    430         }
    431     }
    432 
    433     private String getCecOptionKey(int action) {
    434         switch (action) {
    435             case ACTION_HDMI_CONTROL:
    436                 return Global.HDMI_CONTROL_ENABLED;
    437             case ACTION_DEVICE_AUTO_OFF:
    438                 return Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED;
    439             case ACTION_TV_AUTO_ON:
    440                 return Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED;
    441         }
    442         return "";
    443     }
    444 
    445     private static int toInt(boolean enabled) {
    446         return enabled ? ENABLED : DISABLED;
    447     }
    448 
    449     private boolean readCecOption(String key) {
    450         return Global.getInt(getContentResolver(), key, toInt(true)) == ENABLED;
    451     }
    452 
    453     private void writeCecOption(String key, boolean value) {
    454         if (DEBUG) {
    455             Log.d(TAG, "Writing CEC option " + key + " to " + value);
    456         }
    457         Global.putInt(getContentResolver(), key, toInt(value));
    458     }
    459 }
    460