blob: 69c012e3893d11c50cdc8a2a2329298bd7ca9a3a [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();
Donghyun Cho9ccff512016-04-01 14:31:11 +0900168 if (!mService.isControlEnabled() || initiatedByCec || !mAutoTvOff) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900169 return;
170 }
171 switch (standbyAction) {
172 case HdmiControlService.STANDBY_SCREEN_OFF:
Donghyun Cho9ccff512016-04-01 14:31:11 +0900173 mService.sendCecCommand(
174 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900175 break;
176 case HdmiControlService.STANDBY_SHUTDOWN:
177 // ACTION_SHUTDOWN is taken as a signal to power off all the devices.
178 mService.sendCecCommand(
179 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_BROADCAST));
180 break;
181 }
182 }
183
184 @Override
185 @ServiceThreadOnly
186 void setAutoDeviceOff(boolean enabled) {
187 assertRunOnServiceThread();
188 mAutoTvOff = enabled;
189 }
190
Yuncheol Heo38db6292014-07-01 14:15:14 +0900191 @ServiceThreadOnly
Jinsuk Kime26d8332015-01-09 08:55:41 +0900192 void setActiveSource(boolean on) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900193 assertRunOnServiceThread();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900194 mIsActiveSource = on;
195 if (on) {
196 getWakeLock().acquire();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900197 } else {
198 getWakeLock().release();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900199 }
200 }
201
202 @ServiceThreadOnly
Jinsuk Kimbad83932015-02-10 07:10:40 +0900203 private ActiveWakeLock getWakeLock() {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900204 assertRunOnServiceThread();
205 if (mWakeLock == null) {
Jinsuk Kimbad83932015-02-10 07:10:40 +0900206 if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) {
207 mWakeLock = new SystemWakeLock();
208 } else {
209 // Create a dummy lock object that doesn't do anything about wake lock,
210 // hence allows the device to go to sleep even if it's the active source.
211 mWakeLock = new ActiveWakeLock() {
212 @Override
213 public void acquire() { }
214 @Override
215 public void release() { }
216 @Override
217 public boolean isHeld() { return false; }
218 };
219 HdmiLogger.debug("No wakelock is used to keep the display on.");
220 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900221 }
222 return mWakeLock;
223 }
224
225 @Override
226 protected boolean canGoToStandby() {
227 return !getWakeLock().isHeld();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900228 }
229
230 @Override
231 @ServiceThreadOnly
232 protected boolean handleActiveSource(HdmiCecMessage message) {
233 assertRunOnServiceThread();
234 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900235 mayResetActiveSource(physicalAddress);
236 return true; // Broadcast message.
237 }
238
239 private void mayResetActiveSource(int physicalAddress) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900240 if (physicalAddress != mService.getPhysicalAddress()) {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900241 setActiveSource(false);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900242 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900243 }
244
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900245 @ServiceThreadOnly
246 protected boolean handleUserControlPressed(HdmiCecMessage message) {
247 assertRunOnServiceThread();
248 wakeUpIfActiveSource();
249 return super.handleUserControlPressed(message);
250 }
251
Yuncheol Heo38db6292014-07-01 14:15:14 +0900252 @Override
253 @ServiceThreadOnly
254 protected boolean handleSetStreamPath(HdmiCecMessage message) {
255 assertRunOnServiceThread();
256 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900257 maySetActiveSource(physicalAddress);
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900258 maySendActiveSource(message.getSource());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900259 wakeUpIfActiveSource();
260 return true; // Broadcast message.
261 }
262
Jinsuk Kime26d8332015-01-09 08:55:41 +0900263 // Samsung model we tested sends <Routing Change> and <Request Active Source>
264 // in a row, and then changes the input to the internal source if there is no
265 // <Active Source> in response. To handle this, we'll set ActiveSource aggressively.
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900266 @Override
267 @ServiceThreadOnly
268 protected boolean handleRoutingChange(HdmiCecMessage message) {
269 assertRunOnServiceThread();
270 int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
271 maySetActiveSource(newPath);
272 return true; // Broadcast message.
273 }
274
275 @Override
276 @ServiceThreadOnly
277 protected boolean handleRoutingInformation(HdmiCecMessage message) {
278 assertRunOnServiceThread();
279 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
280 maySetActiveSource(physicalAddress);
281 return true; // Broadcast message.
282 }
283
284 private void maySetActiveSource(int physicalAddress) {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900285 setActiveSource(physicalAddress == mService.getPhysicalAddress());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900286 }
287
288 private void wakeUpIfActiveSource() {
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900289 if (!mIsActiveSource) {
290 return;
291 }
292 // Wake up the device if the power is in standby mode, or its screen is off -
293 // which can happen if the device is holding a partial lock.
294 if (mService.isPowerStandbyOrTransient() || !mService.getPowerManager().isScreenOn()) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900295 mService.wakeUp();
296 }
297 }
298
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900299 private void maySendActiveSource(int dest) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900300 if (mIsActiveSource) {
301 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
302 mAddress, mService.getPhysicalAddress()));
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900303 // Always reports menu-status active to receive RCP.
304 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus(
305 mAddress, dest, Constants.MENU_STATE_ACTIVATED));
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900306 }
307 }
308
309 @Override
310 @ServiceThreadOnly
311 protected boolean handleRequestActiveSource(HdmiCecMessage message) {
312 assertRunOnServiceThread();
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900313 maySendActiveSource(message.getSource());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900314 return true; // Broadcast message.
Yuncheol Heo38db6292014-07-01 14:15:14 +0900315 }
316
Terry Heo795415b2014-10-01 15:03:53 +0900317 @ServiceThreadOnly
318 protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
319 assertRunOnServiceThread();
Donghyun Choa09256c2016-03-11 17:35:37 +0900320 if (!SET_MENU_LANGUAGE) {
321 return false;
322 }
Terry Heo795415b2014-10-01 15:03:53 +0900323
324 try {
325 String iso3Language = new String(message.getParams(), 0, 3, "US-ASCII");
Jinsuk Kim8ea452e2015-07-10 13:01:06 +0900326 Locale currentLocale = mService.getContext().getResources().getConfiguration().locale;
327 if (currentLocale.getISO3Language().equals(iso3Language)) {
328 // Do not switch language if the new language is the same as the current one.
329 // This helps avoid accidental country variant switching from en_US to en_AU
330 // due to the limitation of CEC. See the warning below.
331 return true;
332 }
Terry Heo795415b2014-10-01 15:03:53 +0900333
334 // Don't use Locale.getAvailableLocales() since it returns a locale
335 // which is not available on Settings.
336 final List<LocaleInfo> localeInfos = LocalePicker.getAllAssetLocales(
337 mService.getContext(), false);
338 for (LocaleInfo localeInfo : localeInfos) {
339 if (localeInfo.getLocale().getISO3Language().equals(iso3Language)) {
340 // WARNING: CEC adopts ISO/FDIS-2 for language code, while Android requires
341 // additional country variant to pinpoint the locale. This keeps the right
342 // locale from being chosen. 'eng' in the CEC command, for instance,
343 // will always be mapped to en-AU among other variants like en-US, en-GB,
344 // an en-IN, which may not be the expected one.
345 LocalePicker.updateLocale(localeInfo.getLocale());
346 return true;
347 }
348 }
349 Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language);
350 return false;
351 } catch (UnsupportedEncodingException e) {
Donghyun Choa09256c2016-03-11 17:35:37 +0900352 Slog.w(TAG, "Can't handle <Set Menu Language>", e);
Terry Heo795415b2014-10-01 15:03:53 +0900353 return false;
354 }
355 }
356
Yuncheol Heo38db6292014-07-01 14:15:14 +0900357 @Override
358 @ServiceThreadOnly
Jinsuk Kim8d115eb2015-03-18 10:52:13 +0900359 protected void sendStandby(int deviceId) {
360 assertRunOnServiceThread();
361
362 // Playback device can send <Standby> to TV only. Ignore the parameter.
363 int targetAddress = Constants.ADDR_TV;
364 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
365 }
366
367 @Override
368 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900369 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
370 super.disableDevice(initiatedByCec, callback);
371
Yuncheol Heo38db6292014-07-01 14:15:14 +0900372 assertRunOnServiceThread();
373 if (!initiatedByCec && mIsActiveSource) {
374 mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
375 mAddress, mService.getPhysicalAddress()));
376 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900377 setActiveSource(false);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900378 checkIfPendingActionsCleared();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900379 }
Terry Heo959d2db2014-08-28 16:45:41 +0900380
381 @Override
382 protected void dump(final IndentingPrintWriter pw) {
383 super.dump(pw);
384 pw.println("mIsActiveSource: " + mIsActiveSource);
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900385 pw.println("mAutoTvOff:" + mAutoTvOff);
Terry Heo959d2db2014-08-28 16:45:41 +0900386 }
Jinsuk Kimbad83932015-02-10 07:10:40 +0900387
388 // Wrapper interface over PowerManager.WakeLock
389 private interface ActiveWakeLock {
390 void acquire();
391 void release();
392 boolean isHeld();
393 }
394
395 private class SystemWakeLock implements ActiveWakeLock {
396 private final WakeLock mWakeLock;
397 public SystemWakeLock() {
398 mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
399 mWakeLock.setReferenceCounted(false);
400 }
401
402 @Override
403 public void acquire() {
404 mWakeLock.acquire();
405 HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource);
406 }
407
408 @Override
409 public void release() {
410 mWakeLock.release();
411 HdmiLogger.debug("Wake lock released");
412 }
413
414 @Override
415 public boolean isHeld() {
416 return mWakeLock.isHeld();
417 }
418 }
Jinsuk Kim84659ed2014-10-06 23:48:19 +0000419}