blob: 1810963e6789aeae9f4c025bf35e001706d794ab [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;
Amy23be6812019-01-25 17:04:07 -080022import android.hardware.tv.cec.V1_0.SendMessageResult;
Jinsuk Kime26d8332015-01-09 08:55:41 +090023import android.os.PowerManager;
24import android.os.PowerManager.WakeLock;
Jinsuk Kimaf2acf02014-07-11 18:43:04 +090025import android.os.SystemProperties;
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +090026import android.provider.Settings.Global;
Nick Chalkobfb30762019-10-15 16:06:50 -070027import android.sysprop.HdmiProperties;
Jungshik Jang79c58a42014-06-16 16:45:36 +090028import android.util.Slog;
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090029
Amyaefab642018-08-22 19:10:14 -070030import com.android.internal.annotations.VisibleForTesting;
Terry Heo795415b2014-10-01 15:03:53 +090031import com.android.internal.app.LocalePicker;
32import com.android.internal.app.LocalePicker.LocaleInfo;
Terry Heo959d2db2014-08-28 16:45:41 +090033import com.android.internal.util.IndentingPrintWriter;
Jungshik Janga5b74142014-06-23 18:03:10 +090034import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
Amy23be6812019-01-25 17:04:07 -080035import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
Jungshik Janga5b74142014-06-23 18:03:10 +090036
Terry Heo795415b2014-10-01 15:03:53 +090037import java.io.UnsupportedEncodingException;
38import java.util.List;
Jinsuk Kim8ea452e2015-07-10 13:01:06 +090039import java.util.Locale;
Terry Heo795415b2014-10-01 15:03:53 +090040
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090041/**
42 * Represent a logical device of type Playback residing in Android system.
43 */
Amy645c3612018-09-25 10:48:19 -070044public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
Jungshik Jang79c58a42014-06-16 16:45:36 +090045 private static final String TAG = "HdmiCecLocalDevicePlayback";
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090046
Donghyun Choa09256c2016-03-11 17:35:37 +090047 private static final boolean SET_MENU_LANGUAGE =
Nick Chalkobfb30762019-10-15 16:06:50 -070048 HdmiProperties.set_menu_language().orElse(false);
Donghyun Choa09256c2016-03-11 17:35:37 +090049
Jinsuk Kime26d8332015-01-09 08:55:41 +090050 // Used to keep the device awake while it is the active source. For devices that
51 // cannot wake up via CEC commands, this address the inconvenience of having to
Jinsuk Kimbad83932015-02-10 07:10:40 +090052 // turn them on. True by default, and can be disabled (i.e. device can go to sleep
53 // in active device status) by explicitly setting the system property
54 // persist.sys.hdmi.keep_awake to false.
Jinsuk Kime26d8332015-01-09 08:55:41 +090055 // Lazily initialized - should call getWakeLock() to get the instance.
Jinsuk Kimbad83932015-02-10 07:10:40 +090056 private ActiveWakeLock mWakeLock;
Jinsuk Kime26d8332015-01-09 08:55:41 +090057
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +090058 // If true, turn off TV upon standby. False by default.
59 private boolean mAutoTvOff;
60
Amyaefab642018-08-22 19:10:14 -070061 // Local active port number used for Routing Control.
62 // Default 0 means HOME is the current active path. Temp solution only.
63 // TODO(amyjojo): adding system constants for input ports to TIF mapping.
64 private int mLocalActivePath = 0;
65
Jungshik Jang3ee65722014-06-03 16:22:30 +090066 HdmiCecLocalDevicePlayback(HdmiControlService service) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090067 super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +090068
69 mAutoTvOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, false);
70
71 // The option is false by default. Update settings db as well to have the right
72 // initial setting on UI.
73 mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, mAutoTvOff);
Jungshik Jang8b308d92014-05-29 21:52:28 +090074 }
75
76 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +090077 @ServiceThreadOnly
Yuncheol Heofc44e4e2014-08-04 19:41:09 +090078 protected void onAddressAllocated(int logicalAddress, int reason) {
Jungshik Janga5b74142014-06-23 18:03:10 +090079 assertRunOnServiceThread();
Amyb9d7f432018-11-30 15:08:30 -080080 if (reason == mService.INITIATED_BY_ENABLE_CEC) {
81 mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
82 getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST);
83 }
Jungshik Jang3ee65722014-06-03 16:22:30 +090084 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
85 mAddress, mService.getPhysicalAddress(), mDeviceType));
Jinsuk Kimad515e82014-10-07 07:43:44 +090086 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
87 mAddress, mService.getVendorId()));
Amy3af63c32019-05-10 18:32:50 -070088 // Actively send out an OSD name to the TV to update the TV panel in case the TV
89 // does not query the OSD name on time. This is not a required behavior by the spec.
90 // It is used for some TVs that need the OSD name update but don't query it themselves.
91 buildAndSendSetOsdName(Constants.ADDR_TV);
Amy23be6812019-01-25 17:04:07 -080092 if (mService.audioSystem() == null) {
93 // If current device is not a functional audio system device,
94 // send message to potential audio system device in the system to get the system
95 // audio mode status. If no response, set to false.
96 mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
97 mAddress, Constants.ADDR_AUDIO_SYSTEM), new SendMessageCallback() {
98 @Override
99 public void onSendCompleted(int error) {
100 if (error != SendMessageResult.SUCCESS) {
101 HdmiLogger.debug(
102 "AVR did not respond to <Give System Audio Mode Status>");
103 mService.setSystemAudioActivated(false);
104 }
105 }
106 });
107 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900108 startQueuedActions();
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900109 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900110
Jinsuk Kimaf2acf02014-07-11 18:43:04 +0900111 @Override
112 @ServiceThreadOnly
113 protected int getPreferredAddress() {
114 assertRunOnServiceThread();
115 return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
116 Constants.ADDR_UNREGISTERED);
117 }
118
119 @Override
120 @ServiceThreadOnly
121 protected void setPreferredAddress(int addr) {
122 assertRunOnServiceThread();
Amy59c06c12019-01-18 15:35:15 -0800123 mService.writeStringSystemProperty(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
Jinsuk Kimaf2acf02014-07-11 18:43:04 +0900124 String.valueOf(addr));
125 }
126
Jungshik Janga5b74142014-06-23 18:03:10 +0900127 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900128 void queryDisplayStatus(IHdmiControlCallback callback) {
129 assertRunOnServiceThread();
Jinsuk Kimcb802872015-10-13 08:22:09 +0900130 List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
131 if (!actions.isEmpty()) {
132 Slog.i(TAG, "queryDisplayStatus already in progress");
133 actions.get(0).addCallback(callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900134 return;
135 }
Jinsuk Kimcb802872015-10-13 08:22:09 +0900136 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, Constants.ADDR_TV,
137 callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900138 if (action == null) {
139 Slog.w(TAG, "Cannot initiate queryDisplayStatus");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900140 invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900141 return;
142 }
143 addAndStartAction(action);
144 }
145
Jungshik Jang79c58a42014-06-16 16:45:36 +0900146 @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 Kime26d8332015-01-09 08:55:41 +0900152 if (!connected) {
153 getWakeLock().release();
154 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900155 }
156
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900157 @Override
158 @ServiceThreadOnly
159 protected void onStandby(boolean initiatedByCec, int standbyAction) {
160 assertRunOnServiceThread();
Amyc556aa32019-02-13 17:17:31 -0800161 if (!mService.isControlEnabled()) {
162 return;
163 }
164 if (mIsActiveSource) {
165 mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
166 mAddress, mService.getPhysicalAddress()));
167 }
Amy8ecb0012019-05-09 19:06:42 -0700168 // Invalidate the internal active source record when goes to standby
169 // This set will also update mIsActiveSource
170 mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS);
Amyc556aa32019-02-13 17:17:31 -0800171 if (initiatedByCec || !mAutoTvOff) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900172 return;
173 }
174 switch (standbyAction) {
175 case HdmiControlService.STANDBY_SCREEN_OFF:
Donghyun Cho9ccff512016-04-01 14:31:11 +0900176 mService.sendCecCommand(
177 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900178 break;
179 case HdmiControlService.STANDBY_SHUTDOWN:
180 // ACTION_SHUTDOWN is taken as a signal to power off all the devices.
181 mService.sendCecCommand(
182 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_BROADCAST));
183 break;
184 }
185 }
186
187 @Override
188 @ServiceThreadOnly
189 void setAutoDeviceOff(boolean enabled) {
190 assertRunOnServiceThread();
191 mAutoTvOff = enabled;
192 }
193
Yuncheol Heo38db6292014-07-01 14:15:14 +0900194 @ServiceThreadOnly
Amy645c3612018-09-25 10:48:19 -0700195 @VisibleForTesting
Amy9a59d9c2018-08-31 13:47:24 -0700196 void setIsActiveSource(boolean on) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900197 assertRunOnServiceThread();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900198 mIsActiveSource = on;
199 if (on) {
200 getWakeLock().acquire();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900201 } else {
202 getWakeLock().release();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900203 }
204 }
205
206 @ServiceThreadOnly
Jinsuk Kimbad83932015-02-10 07:10:40 +0900207 private ActiveWakeLock getWakeLock() {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900208 assertRunOnServiceThread();
209 if (mWakeLock == null) {
Jinsuk Kimbad83932015-02-10 07:10:40 +0900210 if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) {
211 mWakeLock = new SystemWakeLock();
212 } else {
213 // Create a dummy lock object that doesn't do anything about wake lock,
214 // hence allows the device to go to sleep even if it's the active source.
215 mWakeLock = new ActiveWakeLock() {
216 @Override
217 public void acquire() { }
218 @Override
219 public void release() { }
220 @Override
221 public boolean isHeld() { return false; }
222 };
223 HdmiLogger.debug("No wakelock is used to keep the display on.");
224 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900225 }
226 return mWakeLock;
227 }
228
229 @Override
230 protected boolean canGoToStandby() {
231 return !getWakeLock().isHeld();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900232 }
233
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900234 @ServiceThreadOnly
235 protected boolean handleUserControlPressed(HdmiCecMessage message) {
236 assertRunOnServiceThread();
237 wakeUpIfActiveSource();
238 return super.handleUserControlPressed(message);
239 }
240
Yuncheol Heo38db6292014-07-01 14:15:14 +0900241 @Override
Amy34037422018-09-06 13:21:08 -0700242 protected void wakeUpIfActiveSource() {
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900243 if (!mIsActiveSource) {
244 return;
245 }
246 // Wake up the device if the power is in standby mode, or its screen is off -
247 // which can happen if the device is holding a partial lock.
248 if (mService.isPowerStandbyOrTransient() || !mService.getPowerManager().isScreenOn()) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900249 mService.wakeUp();
250 }
251 }
252
Amy225d55a2018-09-06 11:03:51 -0700253 @Override
254 protected void maySendActiveSource(int dest) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900255 if (mIsActiveSource) {
256 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
257 mAddress, mService.getPhysicalAddress()));
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900258 // Always reports menu-status active to receive RCP.
259 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus(
260 mAddress, dest, Constants.MENU_STATE_ACTIVATED));
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900261 }
262 }
263
Terry Heo795415b2014-10-01 15:03:53 +0900264 @ServiceThreadOnly
265 protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
266 assertRunOnServiceThread();
Donghyun Choa09256c2016-03-11 17:35:37 +0900267 if (!SET_MENU_LANGUAGE) {
268 return false;
269 }
Terry Heo795415b2014-10-01 15:03:53 +0900270
271 try {
272 String iso3Language = new String(message.getParams(), 0, 3, "US-ASCII");
Jinsuk Kim8ea452e2015-07-10 13:01:06 +0900273 Locale currentLocale = mService.getContext().getResources().getConfiguration().locale;
274 if (currentLocale.getISO3Language().equals(iso3Language)) {
275 // Do not switch language if the new language is the same as the current one.
276 // This helps avoid accidental country variant switching from en_US to en_AU
277 // due to the limitation of CEC. See the warning below.
278 return true;
279 }
Terry Heo795415b2014-10-01 15:03:53 +0900280
281 // Don't use Locale.getAvailableLocales() since it returns a locale
282 // which is not available on Settings.
283 final List<LocaleInfo> localeInfos = LocalePicker.getAllAssetLocales(
284 mService.getContext(), false);
285 for (LocaleInfo localeInfo : localeInfos) {
286 if (localeInfo.getLocale().getISO3Language().equals(iso3Language)) {
287 // WARNING: CEC adopts ISO/FDIS-2 for language code, while Android requires
288 // additional country variant to pinpoint the locale. This keeps the right
289 // locale from being chosen. 'eng' in the CEC command, for instance,
290 // will always be mapped to en-AU among other variants like en-US, en-GB,
291 // an en-IN, which may not be the expected one.
292 LocalePicker.updateLocale(localeInfo.getLocale());
293 return true;
294 }
295 }
296 Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language);
297 return false;
298 } catch (UnsupportedEncodingException e) {
Donghyun Choa09256c2016-03-11 17:35:37 +0900299 Slog.w(TAG, "Can't handle <Set Menu Language>", e);
Terry Heo795415b2014-10-01 15:03:53 +0900300 return false;
301 }
302 }
303
Yuncheol Heo38db6292014-07-01 14:15:14 +0900304 @Override
Amy25e23382019-01-25 14:47:06 -0800305 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
306 // System Audio Mode only turns on/off when Audio System broadcasts on/off message.
307 // For device with type 4 and 5, it can set system audio mode on/off
308 // when there is another audio system device connected into the system first.
309 if (message.getDestination() != Constants.ADDR_BROADCAST
310 || message.getSource() != Constants.ADDR_AUDIO_SYSTEM
311 || mService.audioSystem() != null) {
312 return true;
313 }
314 boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message);
315 if (mService.isSystemAudioActivated() != setSystemAudioModeOn) {
316 mService.setSystemAudioActivated(setSystemAudioModeOn);
317 }
318 return true;
319 }
320
321 @Override
Amy23be6812019-01-25 17:04:07 -0800322 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
323 // Only directly addressed System Audio Mode Status message can change internal
324 // system audio mode status.
325 if (message.getDestination() == mAddress
326 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM) {
327 boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message);
328 if (mService.isSystemAudioActivated() != setSystemAudioModeOn) {
329 mService.setSystemAudioActivated(setSystemAudioModeOn);
330 }
331 }
332 return true;
333 }
334
335 @Override
Donghyun Cho7609bc32016-12-02 15:31:52 +0900336 protected int findKeyReceiverAddress() {
337 return Constants.ADDR_TV;
338 }
339
340 @Override
Amy53b8cb02019-01-29 17:22:03 -0800341 protected int findAudioReceiverAddress() {
342 if (mService.isSystemAudioActivated()) {
343 return Constants.ADDR_AUDIO_SYSTEM;
344 }
345 return Constants.ADDR_TV;
346 }
347
348 @Override
Yuncheol Heo38db6292014-07-01 14:15:14 +0900349 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900350 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
351 super.disableDevice(initiatedByCec, callback);
352
Yuncheol Heo38db6292014-07-01 14:15:14 +0900353 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900354 checkIfPendingActionsCleared();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900355 }
Terry Heo959d2db2014-08-28 16:45:41 +0900356
Amyaefab642018-08-22 19:10:14 -0700357 private void routeToPort(int portId) {
358 // TODO(AMYJOJO): route to specific input of the port
359 mLocalActivePath = portId;
360 }
361
362 @VisibleForTesting
363 protected int getLocalActivePath() {
364 return mLocalActivePath;
365 }
366
Terry Heo959d2db2014-08-28 16:45:41 +0900367 @Override
368 protected void dump(final IndentingPrintWriter pw) {
369 super.dump(pw);
370 pw.println("mIsActiveSource: " + mIsActiveSource);
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900371 pw.println("mAutoTvOff:" + mAutoTvOff);
Terry Heo959d2db2014-08-28 16:45:41 +0900372 }
Jinsuk Kimbad83932015-02-10 07:10:40 +0900373
374 // Wrapper interface over PowerManager.WakeLock
375 private interface ActiveWakeLock {
376 void acquire();
377 void release();
378 boolean isHeld();
379 }
380
381 private class SystemWakeLock implements ActiveWakeLock {
382 private final WakeLock mWakeLock;
383 public SystemWakeLock() {
384 mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
385 mWakeLock.setReferenceCounted(false);
386 }
387
388 @Override
389 public void acquire() {
390 mWakeLock.acquire();
391 HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource);
392 }
393
394 @Override
395 public void release() {
396 mWakeLock.release();
397 HdmiLogger.debug("Wake lock released");
398 }
399
400 @Override
401 public boolean isHeld() {
402 return mWakeLock.isHeld();
403 }
404 }
Jinsuk Kim84659ed2014-10-06 23:48:19 +0000405}