blob: a36e6710887ea946944d65c9aac91a1a67d4f133 [file] [log] [blame]
Jinsuk Kim2918e9e2014-05-20 16:45:45 +09001/*
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.server.hdmi;
18
Jinsuk Kimc0c20d02014-07-04 14:34:31 +090019import android.hardware.hdmi.HdmiControlManager;
Yuncheol Heo64bafd92014-08-11 11:17:54 +090020import android.hardware.hdmi.HdmiDeviceInfo;
Jungshik Jang79c58a42014-06-16 16:45:36 +090021import android.hardware.hdmi.IHdmiControlCallback;
Jinsuk Kime26d8332015-01-09 08:55:41 +090022import android.os.PowerManager;
23import android.os.PowerManager.WakeLock;
Jungshik Jang79c58a42014-06-16 16:45:36 +090024import android.os.RemoteException;
Jinsuk Kimaf2acf02014-07-11 18:43:04 +090025import android.os.SystemProperties;
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +090026import android.provider.Settings.Global;
Jungshik Jang79c58a42014-06-16 16:45:36 +090027import android.util.Slog;
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090028
Terry Heo795415b2014-10-01 15:03:53 +090029import com.android.internal.app.LocalePicker;
30import com.android.internal.app.LocalePicker.LocaleInfo;
Terry Heo959d2db2014-08-28 16:45:41 +090031import com.android.internal.util.IndentingPrintWriter;
Jungshik Janga5b74142014-06-23 18:03:10 +090032import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
33
Terry Heo795415b2014-10-01 15:03:53 +090034import java.io.UnsupportedEncodingException;
35import java.util.List;
Jinsuk Kim8ea452e2015-07-10 13:01:06 +090036import java.util.Locale;
Terry Heo795415b2014-10-01 15:03:53 +090037
Jinsuk Kimcb802872015-10-13 08:22:09 +090038import java.util.List;
39
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090040/**
41 * Represent a logical device of type Playback residing in Android system.
42 */
43final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
Jungshik Jang79c58a42014-06-16 16:45:36 +090044 private static final String TAG = "HdmiCecLocalDevicePlayback";
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090045
Jinsuk Kim659c4862015-05-07 12:12:55 +090046 private static final boolean WAKE_ON_HOTPLUG =
47 SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, true);
48
Donghyun Choa09256c2016-03-11 17:35:37 +090049 private static final boolean SET_MENU_LANGUAGE =
50 SystemProperties.getBoolean(Constants.PROPERTY_SET_MENU_LANGUAGE, false);
51
Yuncheol Heo38db6292014-07-01 14:15:14 +090052 private boolean mIsActiveSource = false;
53
Jinsuk Kime26d8332015-01-09 08:55:41 +090054 // Used to keep the device awake while it is the active source. For devices that
55 // cannot wake up via CEC commands, this address the inconvenience of having to
Jinsuk Kimbad83932015-02-10 07:10:40 +090056 // turn them on. True by default, and can be disabled (i.e. device can go to sleep
57 // in active device status) by explicitly setting the system property
58 // persist.sys.hdmi.keep_awake to false.
Jinsuk Kime26d8332015-01-09 08:55:41 +090059 // Lazily initialized - should call getWakeLock() to get the instance.
Jinsuk Kimbad83932015-02-10 07:10:40 +090060 private ActiveWakeLock mWakeLock;
Jinsuk Kime26d8332015-01-09 08:55:41 +090061
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +090062 // If true, turn off TV upon standby. False by default.
63 private boolean mAutoTvOff;
64
Jungshik Jang3ee65722014-06-03 16:22:30 +090065 HdmiCecLocalDevicePlayback(HdmiControlService service) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090066 super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +090067
68 mAutoTvOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, false);
69
70 // The option is false by default. Update settings db as well to have the right
71 // initial setting on UI.
72 mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, mAutoTvOff);
Jungshik Jang8b308d92014-05-29 21:52:28 +090073 }
74
75 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +090076 @ServiceThreadOnly
Yuncheol Heofc44e4e2014-08-04 19:41:09 +090077 protected void onAddressAllocated(int logicalAddress, int reason) {
Jungshik Janga5b74142014-06-23 18:03:10 +090078 assertRunOnServiceThread();
Jungshik Jang3ee65722014-06-03 16:22:30 +090079 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
80 mAddress, mService.getPhysicalAddress(), mDeviceType));
Jinsuk Kimad515e82014-10-07 07:43:44 +090081 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
82 mAddress, mService.getVendorId()));
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +090083 startQueuedActions();
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090084 }
Jungshik Jang79c58a42014-06-16 16:45:36 +090085
Jinsuk Kimaf2acf02014-07-11 18:43:04 +090086 @Override
87 @ServiceThreadOnly
88 protected int getPreferredAddress() {
89 assertRunOnServiceThread();
90 return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
91 Constants.ADDR_UNREGISTERED);
92 }
93
94 @Override
95 @ServiceThreadOnly
96 protected void setPreferredAddress(int addr) {
97 assertRunOnServiceThread();
98 SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
99 String.valueOf(addr));
100 }
101
Jungshik Janga5b74142014-06-23 18:03:10 +0900102 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900103 void oneTouchPlay(IHdmiControlCallback callback) {
104 assertRunOnServiceThread();
Jinsuk Kimcb802872015-10-13 08:22:09 +0900105 List<OneTouchPlayAction> actions = getActions(OneTouchPlayAction.class);
106 if (!actions.isEmpty()) {
107 Slog.i(TAG, "oneTouchPlay already in progress");
108 actions.get(0).addCallback(callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900109 return;
110 }
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900111 OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
112 callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900113 if (action == null) {
114 Slog.w(TAG, "Cannot initiate oneTouchPlay");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900115 invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900116 return;
117 }
118 addAndStartAction(action);
119 }
120
Jungshik Janga5b74142014-06-23 18:03:10 +0900121 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900122 void queryDisplayStatus(IHdmiControlCallback callback) {
123 assertRunOnServiceThread();
Jinsuk Kimcb802872015-10-13 08:22:09 +0900124 List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
125 if (!actions.isEmpty()) {
126 Slog.i(TAG, "queryDisplayStatus already in progress");
127 actions.get(0).addCallback(callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900128 return;
129 }
Jinsuk Kimcb802872015-10-13 08:22:09 +0900130 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, Constants.ADDR_TV,
131 callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900132 if (action == null) {
133 Slog.w(TAG, "Cannot initiate queryDisplayStatus");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900134 invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900135 return;
136 }
137 addAndStartAction(action);
138 }
139
Jungshik Janga5b74142014-06-23 18:03:10 +0900140 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900141 private void invokeCallback(IHdmiControlCallback callback, int result) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900142 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900143 try {
144 callback.onComplete(result);
145 } catch (RemoteException e) {
146 Slog.e(TAG, "Invoking callback failed:" + e);
147 }
148 }
149
150 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900151 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900152 void onHotplug(int portId, boolean connected) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900153 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900154 mCecMessageCache.flushAll();
Yuncheol Heo89ec14e2014-09-16 15:53:59 +0900155 // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
Jinsuk Kim659c4862015-05-07 12:12:55 +0900156 if (WAKE_ON_HOTPLUG && connected && mService.isPowerStandbyOrTransient()) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900157 mService.wakeUp();
158 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900159 if (!connected) {
160 getWakeLock().release();
161 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900162 }
163
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900164 @Override
165 @ServiceThreadOnly
166 protected void onStandby(boolean initiatedByCec, int standbyAction) {
167 assertRunOnServiceThread();
168 if (!mService.isControlEnabled() || initiatedByCec) {
169 return;
170 }
171 switch (standbyAction) {
172 case HdmiControlService.STANDBY_SCREEN_OFF:
173 if (mAutoTvOff) {
174 mService.sendCecCommand(
175 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
176 }
177 break;
178 case HdmiControlService.STANDBY_SHUTDOWN:
179 // ACTION_SHUTDOWN is taken as a signal to power off all the devices.
180 mService.sendCecCommand(
181 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_BROADCAST));
182 break;
183 }
184 }
185
186 @Override
187 @ServiceThreadOnly
188 void setAutoDeviceOff(boolean enabled) {
189 assertRunOnServiceThread();
190 mAutoTvOff = enabled;
191 }
192
Yuncheol Heo38db6292014-07-01 14:15:14 +0900193 @ServiceThreadOnly
Jinsuk Kime26d8332015-01-09 08:55:41 +0900194 void setActiveSource(boolean on) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900195 assertRunOnServiceThread();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900196 mIsActiveSource = on;
197 if (on) {
198 getWakeLock().acquire();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900199 } else {
200 getWakeLock().release();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900201 }
202 }
203
204 @ServiceThreadOnly
Jinsuk Kimbad83932015-02-10 07:10:40 +0900205 private ActiveWakeLock getWakeLock() {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900206 assertRunOnServiceThread();
207 if (mWakeLock == null) {
Jinsuk Kimbad83932015-02-10 07:10:40 +0900208 if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) {
209 mWakeLock = new SystemWakeLock();
210 } else {
211 // Create a dummy lock object that doesn't do anything about wake lock,
212 // hence allows the device to go to sleep even if it's the active source.
213 mWakeLock = new ActiveWakeLock() {
214 @Override
215 public void acquire() { }
216 @Override
217 public void release() { }
218 @Override
219 public boolean isHeld() { return false; }
220 };
221 HdmiLogger.debug("No wakelock is used to keep the display on.");
222 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900223 }
224 return mWakeLock;
225 }
226
227 @Override
228 protected boolean canGoToStandby() {
229 return !getWakeLock().isHeld();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900230 }
231
232 @Override
233 @ServiceThreadOnly
234 protected boolean handleActiveSource(HdmiCecMessage message) {
235 assertRunOnServiceThread();
236 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900237 mayResetActiveSource(physicalAddress);
238 return true; // Broadcast message.
239 }
240
241 private void mayResetActiveSource(int physicalAddress) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900242 if (physicalAddress != mService.getPhysicalAddress()) {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900243 setActiveSource(false);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900244 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900245 }
246
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900247 @ServiceThreadOnly
248 protected boolean handleUserControlPressed(HdmiCecMessage message) {
249 assertRunOnServiceThread();
250 wakeUpIfActiveSource();
251 return super.handleUserControlPressed(message);
252 }
253
Yuncheol Heo38db6292014-07-01 14:15:14 +0900254 @Override
255 @ServiceThreadOnly
256 protected boolean handleSetStreamPath(HdmiCecMessage message) {
257 assertRunOnServiceThread();
258 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900259 maySetActiveSource(physicalAddress);
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900260 maySendActiveSource(message.getSource());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900261 wakeUpIfActiveSource();
262 return true; // Broadcast message.
263 }
264
Jinsuk Kime26d8332015-01-09 08:55:41 +0900265 // Samsung model we tested sends <Routing Change> and <Request Active Source>
266 // in a row, and then changes the input to the internal source if there is no
267 // <Active Source> in response. To handle this, we'll set ActiveSource aggressively.
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900268 @Override
269 @ServiceThreadOnly
270 protected boolean handleRoutingChange(HdmiCecMessage message) {
271 assertRunOnServiceThread();
272 int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
273 maySetActiveSource(newPath);
274 return true; // Broadcast message.
275 }
276
277 @Override
278 @ServiceThreadOnly
279 protected boolean handleRoutingInformation(HdmiCecMessage message) {
280 assertRunOnServiceThread();
281 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
282 maySetActiveSource(physicalAddress);
283 return true; // Broadcast message.
284 }
285
286 private void maySetActiveSource(int physicalAddress) {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900287 setActiveSource(physicalAddress == mService.getPhysicalAddress());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900288 }
289
290 private void wakeUpIfActiveSource() {
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900291 if (!mIsActiveSource) {
292 return;
293 }
294 // Wake up the device if the power is in standby mode, or its screen is off -
295 // which can happen if the device is holding a partial lock.
296 if (mService.isPowerStandbyOrTransient() || !mService.getPowerManager().isScreenOn()) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900297 mService.wakeUp();
298 }
299 }
300
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900301 private void maySendActiveSource(int dest) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900302 if (mIsActiveSource) {
303 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
304 mAddress, mService.getPhysicalAddress()));
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900305 // Always reports menu-status active to receive RCP.
306 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus(
307 mAddress, dest, Constants.MENU_STATE_ACTIVATED));
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900308 }
309 }
310
311 @Override
312 @ServiceThreadOnly
313 protected boolean handleRequestActiveSource(HdmiCecMessage message) {
314 assertRunOnServiceThread();
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900315 maySendActiveSource(message.getSource());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900316 return true; // Broadcast message.
Yuncheol Heo38db6292014-07-01 14:15:14 +0900317 }
318
Terry Heo795415b2014-10-01 15:03:53 +0900319 @ServiceThreadOnly
320 protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
321 assertRunOnServiceThread();
Donghyun Choa09256c2016-03-11 17:35:37 +0900322 if (!SET_MENU_LANGUAGE) {
323 return false;
324 }
Terry Heo795415b2014-10-01 15:03:53 +0900325
326 try {
327 String iso3Language = new String(message.getParams(), 0, 3, "US-ASCII");
Jinsuk Kim8ea452e2015-07-10 13:01:06 +0900328 Locale currentLocale = mService.getContext().getResources().getConfiguration().locale;
329 if (currentLocale.getISO3Language().equals(iso3Language)) {
330 // Do not switch language if the new language is the same as the current one.
331 // This helps avoid accidental country variant switching from en_US to en_AU
332 // due to the limitation of CEC. See the warning below.
333 return true;
334 }
Terry Heo795415b2014-10-01 15:03:53 +0900335
336 // Don't use Locale.getAvailableLocales() since it returns a locale
337 // which is not available on Settings.
338 final List<LocaleInfo> localeInfos = LocalePicker.getAllAssetLocales(
339 mService.getContext(), false);
340 for (LocaleInfo localeInfo : localeInfos) {
341 if (localeInfo.getLocale().getISO3Language().equals(iso3Language)) {
342 // WARNING: CEC adopts ISO/FDIS-2 for language code, while Android requires
343 // additional country variant to pinpoint the locale. This keeps the right
344 // locale from being chosen. 'eng' in the CEC command, for instance,
345 // will always be mapped to en-AU among other variants like en-US, en-GB,
346 // an en-IN, which may not be the expected one.
347 LocalePicker.updateLocale(localeInfo.getLocale());
348 return true;
349 }
350 }
351 Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language);
352 return false;
353 } catch (UnsupportedEncodingException e) {
Donghyun Choa09256c2016-03-11 17:35:37 +0900354 Slog.w(TAG, "Can't handle <Set Menu Language>", e);
Terry Heo795415b2014-10-01 15:03:53 +0900355 return false;
356 }
357 }
358
Yuncheol Heo38db6292014-07-01 14:15:14 +0900359 @Override
360 @ServiceThreadOnly
Jinsuk Kim8d115eb2015-03-18 10:52:13 +0900361 protected void sendStandby(int deviceId) {
362 assertRunOnServiceThread();
363
364 // Playback device can send <Standby> to TV only. Ignore the parameter.
365 int targetAddress = Constants.ADDR_TV;
366 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
367 }
368
369 @Override
370 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900371 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
372 super.disableDevice(initiatedByCec, callback);
373
Yuncheol Heo38db6292014-07-01 14:15:14 +0900374 assertRunOnServiceThread();
375 if (!initiatedByCec && mIsActiveSource) {
376 mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
377 mAddress, mService.getPhysicalAddress()));
378 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900379 setActiveSource(false);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900380 checkIfPendingActionsCleared();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900381 }
Terry Heo959d2db2014-08-28 16:45:41 +0900382
383 @Override
384 protected void dump(final IndentingPrintWriter pw) {
385 super.dump(pw);
386 pw.println("mIsActiveSource: " + mIsActiveSource);
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900387 pw.println("mAutoTvOff:" + mAutoTvOff);
Terry Heo959d2db2014-08-28 16:45:41 +0900388 }
Jinsuk Kimbad83932015-02-10 07:10:40 +0900389
390 // Wrapper interface over PowerManager.WakeLock
391 private interface ActiveWakeLock {
392 void acquire();
393 void release();
394 boolean isHeld();
395 }
396
397 private class SystemWakeLock implements ActiveWakeLock {
398 private final WakeLock mWakeLock;
399 public SystemWakeLock() {
400 mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
401 mWakeLock.setReferenceCounted(false);
402 }
403
404 @Override
405 public void acquire() {
406 mWakeLock.acquire();
407 HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource);
408 }
409
410 @Override
411 public void release() {
412 mWakeLock.release();
413 HdmiLogger.debug("Wake lock released");
414 }
415
416 @Override
417 public boolean isHeld() {
418 return mWakeLock.isHeld();
419 }
420 }
Jinsuk Kim84659ed2014-10-06 23:48:19 +0000421}