blob: 5541b116cf54ae4230f8325dc57d83319f2a7db7 [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
Jinsuk Kim659c4862015-05-07 12:12:55 +090047 private static final boolean WAKE_ON_HOTPLUG =
Nick Chalko9a6d9852020-02-13 16:56:35 -080048 SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, false);
Jinsuk Kim659c4862015-05-07 12:12:55 +090049
Donghyun Choa09256c2016-03-11 17:35:37 +090050 private static final boolean SET_MENU_LANGUAGE =
Nick Chalkobfb30762019-10-15 16:06:50 -070051 HdmiProperties.set_menu_language().orElse(false);
Donghyun Choa09256c2016-03-11 17:35:37 +090052
Jinsuk Kime26d8332015-01-09 08:55:41 +090053 // Used to keep the device awake while it is the active source. For devices that
54 // cannot wake up via CEC commands, this address the inconvenience of having to
Jinsuk Kimbad83932015-02-10 07:10:40 +090055 // turn them on. True by default, and can be disabled (i.e. device can go to sleep
56 // in active device status) by explicitly setting the system property
57 // persist.sys.hdmi.keep_awake to false.
Jinsuk Kime26d8332015-01-09 08:55:41 +090058 // Lazily initialized - should call getWakeLock() to get the instance.
Jinsuk Kimbad83932015-02-10 07:10:40 +090059 private ActiveWakeLock mWakeLock;
Jinsuk Kime26d8332015-01-09 08:55:41 +090060
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +090061 // If true, turn off TV upon standby. False by default.
62 private boolean mAutoTvOff;
63
Amyaefab642018-08-22 19:10:14 -070064 // Local active port number used for Routing Control.
65 // Default 0 means HOME is the current active path. Temp solution only.
66 // TODO(amyjojo): adding system constants for input ports to TIF mapping.
67 private int mLocalActivePath = 0;
68
Jungshik Jang3ee65722014-06-03 16:22:30 +090069 HdmiCecLocalDevicePlayback(HdmiControlService service) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090070 super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +090071
72 mAutoTvOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, false);
73
74 // The option is false by default. Update settings db as well to have the right
75 // initial setting on UI.
76 mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, mAutoTvOff);
Jungshik Jang8b308d92014-05-29 21:52:28 +090077 }
78
79 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +090080 @ServiceThreadOnly
Yuncheol Heofc44e4e2014-08-04 19:41:09 +090081 protected void onAddressAllocated(int logicalAddress, int reason) {
Jungshik Janga5b74142014-06-23 18:03:10 +090082 assertRunOnServiceThread();
Amyb9d7f432018-11-30 15:08:30 -080083 if (reason == mService.INITIATED_BY_ENABLE_CEC) {
84 mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
85 getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST);
86 }
Jungshik Jang3ee65722014-06-03 16:22:30 +090087 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
88 mAddress, mService.getPhysicalAddress(), mDeviceType));
Jinsuk Kimad515e82014-10-07 07:43:44 +090089 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
90 mAddress, mService.getVendorId()));
Amy3af63c32019-05-10 18:32:50 -070091 // Actively send out an OSD name to the TV to update the TV panel in case the TV
92 // does not query the OSD name on time. This is not a required behavior by the spec.
93 // It is used for some TVs that need the OSD name update but don't query it themselves.
94 buildAndSendSetOsdName(Constants.ADDR_TV);
Amy23be6812019-01-25 17:04:07 -080095 if (mService.audioSystem() == null) {
96 // If current device is not a functional audio system device,
97 // send message to potential audio system device in the system to get the system
98 // audio mode status. If no response, set to false.
99 mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
100 mAddress, Constants.ADDR_AUDIO_SYSTEM), new SendMessageCallback() {
101 @Override
102 public void onSendCompleted(int error) {
103 if (error != SendMessageResult.SUCCESS) {
104 HdmiLogger.debug(
105 "AVR did not respond to <Give System Audio Mode Status>");
106 mService.setSystemAudioActivated(false);
107 }
108 }
109 });
110 }
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +0900111 startQueuedActions();
Jinsuk Kim2918e9e2014-05-20 16:45:45 +0900112 }
Jungshik Jang79c58a42014-06-16 16:45:36 +0900113
Jinsuk Kimaf2acf02014-07-11 18:43:04 +0900114 @Override
115 @ServiceThreadOnly
116 protected int getPreferredAddress() {
117 assertRunOnServiceThread();
118 return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
119 Constants.ADDR_UNREGISTERED);
120 }
121
122 @Override
123 @ServiceThreadOnly
124 protected void setPreferredAddress(int addr) {
125 assertRunOnServiceThread();
Amy59c06c12019-01-18 15:35:15 -0800126 mService.writeStringSystemProperty(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
Jinsuk Kimaf2acf02014-07-11 18:43:04 +0900127 String.valueOf(addr));
128 }
129
Jungshik Janga5b74142014-06-23 18:03:10 +0900130 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900131 void queryDisplayStatus(IHdmiControlCallback callback) {
132 assertRunOnServiceThread();
Jinsuk Kimcb802872015-10-13 08:22:09 +0900133 List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
134 if (!actions.isEmpty()) {
135 Slog.i(TAG, "queryDisplayStatus already in progress");
136 actions.get(0).addCallback(callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900137 return;
138 }
Jinsuk Kimcb802872015-10-13 08:22:09 +0900139 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, Constants.ADDR_TV,
140 callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900141 if (action == null) {
142 Slog.w(TAG, "Cannot initiate queryDisplayStatus");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900143 invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900144 return;
145 }
146 addAndStartAction(action);
147 }
148
Jungshik Jang79c58a42014-06-16 16:45:36 +0900149 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900150 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900151 void onHotplug(int portId, boolean connected) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900152 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900153 mCecMessageCache.flushAll();
Yuncheol Heo89ec14e2014-09-16 15:53:59 +0900154 // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
Jinsuk Kim659c4862015-05-07 12:12:55 +0900155 if (WAKE_ON_HOTPLUG && connected && mService.isPowerStandbyOrTransient()) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900156 mService.wakeUp();
157 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900158 if (!connected) {
159 getWakeLock().release();
160 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900161 }
162
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900163 @Override
164 @ServiceThreadOnly
165 protected void onStandby(boolean initiatedByCec, int standbyAction) {
166 assertRunOnServiceThread();
Amyc556aa32019-02-13 17:17:31 -0800167 if (!mService.isControlEnabled()) {
168 return;
169 }
170 if (mIsActiveSource) {
171 mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
172 mAddress, mService.getPhysicalAddress()));
173 }
Amy8ecb0012019-05-09 19:06:42 -0700174 // Invalidate the internal active source record when goes to standby
175 // This set will also update mIsActiveSource
176 mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS);
Amyc556aa32019-02-13 17:17:31 -0800177 if (initiatedByCec || !mAutoTvOff) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900178 return;
179 }
180 switch (standbyAction) {
181 case HdmiControlService.STANDBY_SCREEN_OFF:
Donghyun Cho9ccff512016-04-01 14:31:11 +0900182 mService.sendCecCommand(
183 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900184 break;
185 case HdmiControlService.STANDBY_SHUTDOWN:
186 // ACTION_SHUTDOWN is taken as a signal to power off all the devices.
187 mService.sendCecCommand(
188 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_BROADCAST));
189 break;
190 }
191 }
192
193 @Override
194 @ServiceThreadOnly
195 void setAutoDeviceOff(boolean enabled) {
196 assertRunOnServiceThread();
197 mAutoTvOff = enabled;
198 }
199
Yuncheol Heo38db6292014-07-01 14:15:14 +0900200 @ServiceThreadOnly
Amy645c3612018-09-25 10:48:19 -0700201 @VisibleForTesting
Amy9a59d9c2018-08-31 13:47:24 -0700202 void setIsActiveSource(boolean on) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900203 assertRunOnServiceThread();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900204 mIsActiveSource = on;
205 if (on) {
206 getWakeLock().acquire();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900207 } else {
208 getWakeLock().release();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900209 }
210 }
211
212 @ServiceThreadOnly
Jinsuk Kimbad83932015-02-10 07:10:40 +0900213 private ActiveWakeLock getWakeLock() {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900214 assertRunOnServiceThread();
215 if (mWakeLock == null) {
Jinsuk Kimbad83932015-02-10 07:10:40 +0900216 if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) {
217 mWakeLock = new SystemWakeLock();
218 } else {
219 // Create a dummy lock object that doesn't do anything about wake lock,
220 // hence allows the device to go to sleep even if it's the active source.
221 mWakeLock = new ActiveWakeLock() {
222 @Override
223 public void acquire() { }
224 @Override
225 public void release() { }
226 @Override
227 public boolean isHeld() { return false; }
228 };
229 HdmiLogger.debug("No wakelock is used to keep the display on.");
230 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900231 }
232 return mWakeLock;
233 }
234
235 @Override
236 protected boolean canGoToStandby() {
237 return !getWakeLock().isHeld();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900238 }
239
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900240 @ServiceThreadOnly
241 protected boolean handleUserControlPressed(HdmiCecMessage message) {
242 assertRunOnServiceThread();
243 wakeUpIfActiveSource();
244 return super.handleUserControlPressed(message);
245 }
246
Yuncheol Heo38db6292014-07-01 14:15:14 +0900247 @Override
Amy34037422018-09-06 13:21:08 -0700248 protected void wakeUpIfActiveSource() {
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900249 if (!mIsActiveSource) {
250 return;
251 }
252 // Wake up the device if the power is in standby mode, or its screen is off -
253 // which can happen if the device is holding a partial lock.
254 if (mService.isPowerStandbyOrTransient() || !mService.getPowerManager().isScreenOn()) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900255 mService.wakeUp();
256 }
257 }
258
Amy225d55a2018-09-06 11:03:51 -0700259 @Override
260 protected void maySendActiveSource(int dest) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900261 if (mIsActiveSource) {
262 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
263 mAddress, mService.getPhysicalAddress()));
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900264 // Always reports menu-status active to receive RCP.
265 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus(
266 mAddress, dest, Constants.MENU_STATE_ACTIVATED));
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900267 }
268 }
269
Terry Heo795415b2014-10-01 15:03:53 +0900270 @ServiceThreadOnly
271 protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
272 assertRunOnServiceThread();
Donghyun Choa09256c2016-03-11 17:35:37 +0900273 if (!SET_MENU_LANGUAGE) {
274 return false;
275 }
Terry Heo795415b2014-10-01 15:03:53 +0900276
277 try {
278 String iso3Language = new String(message.getParams(), 0, 3, "US-ASCII");
Jinsuk Kim8ea452e2015-07-10 13:01:06 +0900279 Locale currentLocale = mService.getContext().getResources().getConfiguration().locale;
280 if (currentLocale.getISO3Language().equals(iso3Language)) {
281 // Do not switch language if the new language is the same as the current one.
282 // This helps avoid accidental country variant switching from en_US to en_AU
283 // due to the limitation of CEC. See the warning below.
284 return true;
285 }
Terry Heo795415b2014-10-01 15:03:53 +0900286
287 // Don't use Locale.getAvailableLocales() since it returns a locale
288 // which is not available on Settings.
289 final List<LocaleInfo> localeInfos = LocalePicker.getAllAssetLocales(
290 mService.getContext(), false);
291 for (LocaleInfo localeInfo : localeInfos) {
292 if (localeInfo.getLocale().getISO3Language().equals(iso3Language)) {
293 // WARNING: CEC adopts ISO/FDIS-2 for language code, while Android requires
294 // additional country variant to pinpoint the locale. This keeps the right
295 // locale from being chosen. 'eng' in the CEC command, for instance,
296 // will always be mapped to en-AU among other variants like en-US, en-GB,
297 // an en-IN, which may not be the expected one.
298 LocalePicker.updateLocale(localeInfo.getLocale());
299 return true;
300 }
301 }
302 Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language);
303 return false;
304 } catch (UnsupportedEncodingException e) {
Donghyun Choa09256c2016-03-11 17:35:37 +0900305 Slog.w(TAG, "Can't handle <Set Menu Language>", e);
Terry Heo795415b2014-10-01 15:03:53 +0900306 return false;
307 }
308 }
309
Yuncheol Heo38db6292014-07-01 14:15:14 +0900310 @Override
Amy25e23382019-01-25 14:47:06 -0800311 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
312 // System Audio Mode only turns on/off when Audio System broadcasts on/off message.
313 // For device with type 4 and 5, it can set system audio mode on/off
314 // when there is another audio system device connected into the system first.
315 if (message.getDestination() != Constants.ADDR_BROADCAST
316 || message.getSource() != Constants.ADDR_AUDIO_SYSTEM
317 || mService.audioSystem() != null) {
318 return true;
319 }
320 boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message);
321 if (mService.isSystemAudioActivated() != setSystemAudioModeOn) {
322 mService.setSystemAudioActivated(setSystemAudioModeOn);
323 }
324 return true;
325 }
326
327 @Override
Amy23be6812019-01-25 17:04:07 -0800328 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
329 // Only directly addressed System Audio Mode Status message can change internal
330 // system audio mode status.
331 if (message.getDestination() == mAddress
332 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM) {
333 boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message);
334 if (mService.isSystemAudioActivated() != setSystemAudioModeOn) {
335 mService.setSystemAudioActivated(setSystemAudioModeOn);
336 }
337 }
338 return true;
339 }
340
341 @Override
Donghyun Cho7609bc32016-12-02 15:31:52 +0900342 protected int findKeyReceiverAddress() {
343 return Constants.ADDR_TV;
344 }
345
346 @Override
Amy53b8cb02019-01-29 17:22:03 -0800347 protected int findAudioReceiverAddress() {
348 if (mService.isSystemAudioActivated()) {
349 return Constants.ADDR_AUDIO_SYSTEM;
350 }
351 return Constants.ADDR_TV;
352 }
353
354 @Override
Yuncheol Heo38db6292014-07-01 14:15:14 +0900355 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900356 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
357 super.disableDevice(initiatedByCec, callback);
358
Yuncheol Heo38db6292014-07-01 14:15:14 +0900359 assertRunOnServiceThread();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900360 checkIfPendingActionsCleared();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900361 }
Terry Heo959d2db2014-08-28 16:45:41 +0900362
Amyaefab642018-08-22 19:10:14 -0700363 private void routeToPort(int portId) {
364 // TODO(AMYJOJO): route to specific input of the port
365 mLocalActivePath = portId;
366 }
367
368 @VisibleForTesting
369 protected int getLocalActivePath() {
370 return mLocalActivePath;
371 }
372
Terry Heo959d2db2014-08-28 16:45:41 +0900373 @Override
374 protected void dump(final IndentingPrintWriter pw) {
375 super.dump(pw);
376 pw.println("mIsActiveSource: " + mIsActiveSource);
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900377 pw.println("mAutoTvOff:" + mAutoTvOff);
Terry Heo959d2db2014-08-28 16:45:41 +0900378 }
Jinsuk Kimbad83932015-02-10 07:10:40 +0900379
380 // Wrapper interface over PowerManager.WakeLock
381 private interface ActiveWakeLock {
382 void acquire();
383 void release();
384 boolean isHeld();
385 }
386
387 private class SystemWakeLock implements ActiveWakeLock {
388 private final WakeLock mWakeLock;
389 public SystemWakeLock() {
390 mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
391 mWakeLock.setReferenceCounted(false);
392 }
393
394 @Override
395 public void acquire() {
396 mWakeLock.acquire();
397 HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource);
398 }
399
400 @Override
401 public void release() {
402 mWakeLock.release();
403 HdmiLogger.debug("Wake lock released");
404 }
405
406 @Override
407 public boolean isHeld() {
408 return mWakeLock.isHeld();
409 }
410 }
Jinsuk Kim84659ed2014-10-06 23:48:19 +0000411}