blob: 3c35f5e0ef5d11013aa58c95a04dcf782bbdb766 [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 Kim2918e9e2014-05-20 16:45:45 +090038/**
39 * Represent a logical device of type Playback residing in Android system.
40 */
41final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
Jungshik Jang79c58a42014-06-16 16:45:36 +090042 private static final String TAG = "HdmiCecLocalDevicePlayback";
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090043
Jinsuk Kim659c4862015-05-07 12:12:55 +090044 private static final boolean WAKE_ON_HOTPLUG =
45 SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, true);
46
Yuncheol Heo38db6292014-07-01 14:15:14 +090047 private boolean mIsActiveSource = false;
48
Jinsuk Kime26d8332015-01-09 08:55:41 +090049 // Used to keep the device awake while it is the active source. For devices that
50 // cannot wake up via CEC commands, this address the inconvenience of having to
Jinsuk Kimbad83932015-02-10 07:10:40 +090051 // turn them on. True by default, and can be disabled (i.e. device can go to sleep
52 // in active device status) by explicitly setting the system property
53 // persist.sys.hdmi.keep_awake to false.
Jinsuk Kime26d8332015-01-09 08:55:41 +090054 // Lazily initialized - should call getWakeLock() to get the instance.
Jinsuk Kimbad83932015-02-10 07:10:40 +090055 private ActiveWakeLock mWakeLock;
Jinsuk Kime26d8332015-01-09 08:55:41 +090056
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +090057 // If true, turn off TV upon standby. False by default.
58 private boolean mAutoTvOff;
59
Jungshik Jang3ee65722014-06-03 16:22:30 +090060 HdmiCecLocalDevicePlayback(HdmiControlService service) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090061 super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +090062
63 mAutoTvOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, false);
64
65 // The option is false by default. Update settings db as well to have the right
66 // initial setting on UI.
67 mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, mAutoTvOff);
Jungshik Jang8b308d92014-05-29 21:52:28 +090068 }
69
70 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +090071 @ServiceThreadOnly
Yuncheol Heofc44e4e2014-08-04 19:41:09 +090072 protected void onAddressAllocated(int logicalAddress, int reason) {
Jungshik Janga5b74142014-06-23 18:03:10 +090073 assertRunOnServiceThread();
Jungshik Jang3ee65722014-06-03 16:22:30 +090074 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
75 mAddress, mService.getPhysicalAddress(), mDeviceType));
Jinsuk Kimad515e82014-10-07 07:43:44 +090076 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
77 mAddress, mService.getVendorId()));
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +090078 startQueuedActions();
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090079 }
Jungshik Jang79c58a42014-06-16 16:45:36 +090080
Jinsuk Kimaf2acf02014-07-11 18:43:04 +090081 @Override
82 @ServiceThreadOnly
83 protected int getPreferredAddress() {
84 assertRunOnServiceThread();
85 return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
86 Constants.ADDR_UNREGISTERED);
87 }
88
89 @Override
90 @ServiceThreadOnly
91 protected void setPreferredAddress(int addr) {
92 assertRunOnServiceThread();
93 SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
94 String.valueOf(addr));
95 }
96
Jungshik Janga5b74142014-06-23 18:03:10 +090097 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +090098 void oneTouchPlay(IHdmiControlCallback callback) {
99 assertRunOnServiceThread();
100 if (hasAction(OneTouchPlayAction.class)) {
101 Slog.w(TAG, "oneTouchPlay already in progress");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900102 invokeCallback(callback, HdmiControlManager.RESULT_ALREADY_IN_PROGRESS);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900103 return;
104 }
105
106 // TODO: Consider the case of multiple TV sets. For now we always direct the command
107 // to the primary one.
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900108 OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
109 callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900110 if (action == null) {
111 Slog.w(TAG, "Cannot initiate oneTouchPlay");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900112 invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900113 return;
114 }
115 addAndStartAction(action);
116 }
117
Jungshik Janga5b74142014-06-23 18:03:10 +0900118 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900119 void queryDisplayStatus(IHdmiControlCallback callback) {
120 assertRunOnServiceThread();
121 if (hasAction(DevicePowerStatusAction.class)) {
122 Slog.w(TAG, "queryDisplayStatus already in progress");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900123 invokeCallback(callback, HdmiControlManager.RESULT_ALREADY_IN_PROGRESS);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900124 return;
125 }
126 DevicePowerStatusAction action = DevicePowerStatusAction.create(this,
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900127 Constants.ADDR_TV, callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900128 if (action == null) {
129 Slog.w(TAG, "Cannot initiate queryDisplayStatus");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900130 invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900131 return;
132 }
133 addAndStartAction(action);
134 }
135
Jungshik Janga5b74142014-06-23 18:03:10 +0900136 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900137 private void invokeCallback(IHdmiControlCallback callback, int result) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900138 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900139 try {
140 callback.onComplete(result);
141 } catch (RemoteException e) {
142 Slog.e(TAG, "Invoking callback failed:" + e);
143 }
144 }
145
146 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900147 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900148 void onHotplug(int portId, boolean connected) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900149 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900150 mCecMessageCache.flushAll();
Yuncheol Heo89ec14e2014-09-16 15:53:59 +0900151 // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
Jinsuk Kim659c4862015-05-07 12:12:55 +0900152 if (WAKE_ON_HOTPLUG && connected && mService.isPowerStandbyOrTransient()) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900153 mService.wakeUp();
154 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900155 if (!connected) {
156 getWakeLock().release();
157 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900158 }
159
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900160 @Override
161 @ServiceThreadOnly
162 protected void onStandby(boolean initiatedByCec, int standbyAction) {
163 assertRunOnServiceThread();
164 if (!mService.isControlEnabled() || initiatedByCec) {
165 return;
166 }
167 switch (standbyAction) {
168 case HdmiControlService.STANDBY_SCREEN_OFF:
169 if (mAutoTvOff) {
170 mService.sendCecCommand(
171 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
172 }
173 break;
174 case HdmiControlService.STANDBY_SHUTDOWN:
175 // ACTION_SHUTDOWN is taken as a signal to power off all the devices.
176 mService.sendCecCommand(
177 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_BROADCAST));
178 break;
179 }
180 }
181
182 @Override
183 @ServiceThreadOnly
184 void setAutoDeviceOff(boolean enabled) {
185 assertRunOnServiceThread();
186 mAutoTvOff = enabled;
187 }
188
Yuncheol Heo38db6292014-07-01 14:15:14 +0900189 @ServiceThreadOnly
Jinsuk Kime26d8332015-01-09 08:55:41 +0900190 void setActiveSource(boolean on) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900191 assertRunOnServiceThread();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900192 mIsActiveSource = on;
193 if (on) {
194 getWakeLock().acquire();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900195 } else {
196 getWakeLock().release();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900197 }
198 }
199
200 @ServiceThreadOnly
Jinsuk Kimbad83932015-02-10 07:10:40 +0900201 private ActiveWakeLock getWakeLock() {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900202 assertRunOnServiceThread();
203 if (mWakeLock == null) {
Jinsuk Kimbad83932015-02-10 07:10:40 +0900204 if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) {
205 mWakeLock = new SystemWakeLock();
206 } else {
207 // Create a dummy lock object that doesn't do anything about wake lock,
208 // hence allows the device to go to sleep even if it's the active source.
209 mWakeLock = new ActiveWakeLock() {
210 @Override
211 public void acquire() { }
212 @Override
213 public void release() { }
214 @Override
215 public boolean isHeld() { return false; }
216 };
217 HdmiLogger.debug("No wakelock is used to keep the display on.");
218 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900219 }
220 return mWakeLock;
221 }
222
223 @Override
224 protected boolean canGoToStandby() {
225 return !getWakeLock().isHeld();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900226 }
227
228 @Override
229 @ServiceThreadOnly
230 protected boolean handleActiveSource(HdmiCecMessage message) {
231 assertRunOnServiceThread();
232 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900233 mayResetActiveSource(physicalAddress);
234 return true; // Broadcast message.
235 }
236
237 private void mayResetActiveSource(int physicalAddress) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900238 if (physicalAddress != mService.getPhysicalAddress()) {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900239 setActiveSource(false);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900240 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900241 }
242
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900243 @ServiceThreadOnly
244 protected boolean handleUserControlPressed(HdmiCecMessage message) {
245 assertRunOnServiceThread();
246 wakeUpIfActiveSource();
247 return super.handleUserControlPressed(message);
248 }
249
Yuncheol Heo38db6292014-07-01 14:15:14 +0900250 @Override
251 @ServiceThreadOnly
252 protected boolean handleSetStreamPath(HdmiCecMessage message) {
253 assertRunOnServiceThread();
254 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900255 maySetActiveSource(physicalAddress);
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900256 maySendActiveSource(message.getSource());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900257 wakeUpIfActiveSource();
258 return true; // Broadcast message.
259 }
260
Jinsuk Kime26d8332015-01-09 08:55:41 +0900261 // Samsung model we tested sends <Routing Change> and <Request Active Source>
262 // in a row, and then changes the input to the internal source if there is no
263 // <Active Source> in response. To handle this, we'll set ActiveSource aggressively.
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900264 @Override
265 @ServiceThreadOnly
266 protected boolean handleRoutingChange(HdmiCecMessage message) {
267 assertRunOnServiceThread();
268 int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
269 maySetActiveSource(newPath);
270 return true; // Broadcast message.
271 }
272
273 @Override
274 @ServiceThreadOnly
275 protected boolean handleRoutingInformation(HdmiCecMessage message) {
276 assertRunOnServiceThread();
277 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
278 maySetActiveSource(physicalAddress);
279 return true; // Broadcast message.
280 }
281
282 private void maySetActiveSource(int physicalAddress) {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900283 setActiveSource(physicalAddress == mService.getPhysicalAddress());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900284 }
285
286 private void wakeUpIfActiveSource() {
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900287 if (!mIsActiveSource) {
288 return;
289 }
290 // Wake up the device if the power is in standby mode, or its screen is off -
291 // which can happen if the device is holding a partial lock.
292 if (mService.isPowerStandbyOrTransient() || !mService.getPowerManager().isScreenOn()) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900293 mService.wakeUp();
294 }
295 }
296
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900297 private void maySendActiveSource(int dest) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900298 if (mIsActiveSource) {
299 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
300 mAddress, mService.getPhysicalAddress()));
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900301 // Always reports menu-status active to receive RCP.
302 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus(
303 mAddress, dest, Constants.MENU_STATE_ACTIVATED));
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900304 }
305 }
306
307 @Override
308 @ServiceThreadOnly
309 protected boolean handleRequestActiveSource(HdmiCecMessage message) {
310 assertRunOnServiceThread();
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900311 maySendActiveSource(message.getSource());
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900312 return true; // Broadcast message.
Yuncheol Heo38db6292014-07-01 14:15:14 +0900313 }
314
Terry Heo795415b2014-10-01 15:03:53 +0900315 @ServiceThreadOnly
316 protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
317 assertRunOnServiceThread();
318
319 try {
320 String iso3Language = new String(message.getParams(), 0, 3, "US-ASCII");
Jinsuk Kim8ea452e2015-07-10 13:01:06 +0900321 Locale currentLocale = mService.getContext().getResources().getConfiguration().locale;
322 if (currentLocale.getISO3Language().equals(iso3Language)) {
323 // Do not switch language if the new language is the same as the current one.
324 // This helps avoid accidental country variant switching from en_US to en_AU
325 // due to the limitation of CEC. See the warning below.
326 return true;
327 }
Terry Heo795415b2014-10-01 15:03:53 +0900328
329 // Don't use Locale.getAvailableLocales() since it returns a locale
330 // which is not available on Settings.
331 final List<LocaleInfo> localeInfos = LocalePicker.getAllAssetLocales(
332 mService.getContext(), false);
333 for (LocaleInfo localeInfo : localeInfos) {
334 if (localeInfo.getLocale().getISO3Language().equals(iso3Language)) {
335 // WARNING: CEC adopts ISO/FDIS-2 for language code, while Android requires
336 // additional country variant to pinpoint the locale. This keeps the right
337 // locale from being chosen. 'eng' in the CEC command, for instance,
338 // will always be mapped to en-AU among other variants like en-US, en-GB,
339 // an en-IN, which may not be the expected one.
340 LocalePicker.updateLocale(localeInfo.getLocale());
341 return true;
342 }
343 }
344 Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language);
345 return false;
346 } catch (UnsupportedEncodingException e) {
347 return false;
348 }
349 }
350
Yuncheol Heo38db6292014-07-01 14:15:14 +0900351 @Override
352 @ServiceThreadOnly
Jinsuk Kim8d115eb2015-03-18 10:52:13 +0900353 protected void sendStandby(int deviceId) {
354 assertRunOnServiceThread();
355
356 // Playback device can send <Standby> to TV only. Ignore the parameter.
357 int targetAddress = Constants.ADDR_TV;
358 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
359 }
360
361 @Override
362 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900363 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
364 super.disableDevice(initiatedByCec, callback);
365
Yuncheol Heo38db6292014-07-01 14:15:14 +0900366 assertRunOnServiceThread();
367 if (!initiatedByCec && mIsActiveSource) {
368 mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
369 mAddress, mService.getPhysicalAddress()));
370 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900371 setActiveSource(false);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900372 checkIfPendingActionsCleared();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900373 }
Terry Heo959d2db2014-08-28 16:45:41 +0900374
375 @Override
376 protected void dump(final IndentingPrintWriter pw) {
377 super.dump(pw);
378 pw.println("mIsActiveSource: " + mIsActiveSource);
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900379 pw.println("mAutoTvOff:" + mAutoTvOff);
Terry Heo959d2db2014-08-28 16:45:41 +0900380 }
Jinsuk Kimbad83932015-02-10 07:10:40 +0900381
382 // Wrapper interface over PowerManager.WakeLock
383 private interface ActiveWakeLock {
384 void acquire();
385 void release();
386 boolean isHeld();
387 }
388
389 private class SystemWakeLock implements ActiveWakeLock {
390 private final WakeLock mWakeLock;
391 public SystemWakeLock() {
392 mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
393 mWakeLock.setReferenceCounted(false);
394 }
395
396 @Override
397 public void acquire() {
398 mWakeLock.acquire();
399 HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource);
400 }
401
402 @Override
403 public void release() {
404 mWakeLock.release();
405 HdmiLogger.debug("Wake lock released");
406 }
407
408 @Override
409 public boolean isHeld() {
410 return mWakeLock.isHeld();
411 }
412 }
Jinsuk Kim84659ed2014-10-06 23:48:19 +0000413}