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 onCreate(Bundle savedInstanceState)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 createLayout()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 createData(TvInputInfo info)178 private static Bundle createData(TvInputInfo info) { 179 Bundle data = new Bundle(); 180 data.putString(KEY_ID, info.getId()); 181 return data; 182 } 183 createData(TvInputInfo info, String label)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 getOnOffResId(boolean enabled)190 private static int getOnOffResId(boolean enabled) { 191 return enabled ? R.string.on : R.string.off; 192 } 193 getOnOffLayout(final int titleResId, final int descResId, final int action, final Bundle data, final boolean checked)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