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