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