blob: ef7d24159009093919dca16bd8947a9f8a093c77 [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;
Jinsuk Kimaf2acf02014-07-11 18:43:04 +090024import android.os.SystemProperties;
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +090025import android.provider.Settings.Global;
Jungshik Jang79c58a42014-06-16 16:45:36 +090026import android.util.Slog;
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090027
Amyaefab642018-08-22 19:10:14 -070028import com.android.internal.annotations.VisibleForTesting;
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 */
Amy645c3612018-09-25 10:48:19 -070041public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
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
Donghyun Choa09256c2016-03-11 17:35:37 +090047 private static final boolean SET_MENU_LANGUAGE =
48 SystemProperties.getBoolean(Constants.PROPERTY_SET_MENU_LANGUAGE, false);
49
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()));
Jinsuk Kim6f87b4e2014-10-10 14:40:29 +090088 startQueuedActions();
Jinsuk Kim2918e9e2014-05-20 16:45:45 +090089 }
Jungshik Jang79c58a42014-06-16 16:45:36 +090090
Jinsuk Kimaf2acf02014-07-11 18:43:04 +090091 @Override
92 @ServiceThreadOnly
93 protected int getPreferredAddress() {
94 assertRunOnServiceThread();
95 return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
96 Constants.ADDR_UNREGISTERED);
97 }
98
99 @Override
100 @ServiceThreadOnly
101 protected void setPreferredAddress(int addr) {
102 assertRunOnServiceThread();
Amy59c06c12019-01-18 15:35:15 -0800103 mService.writeStringSystemProperty(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
Jinsuk Kimaf2acf02014-07-11 18:43:04 +0900104 String.valueOf(addr));
105 }
106
Jungshik Janga5b74142014-06-23 18:03:10 +0900107 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900108 void queryDisplayStatus(IHdmiControlCallback callback) {
109 assertRunOnServiceThread();
Jinsuk Kimcb802872015-10-13 08:22:09 +0900110 List<DevicePowerStatusAction> actions = getActions(DevicePowerStatusAction.class);
111 if (!actions.isEmpty()) {
112 Slog.i(TAG, "queryDisplayStatus already in progress");
113 actions.get(0).addCallback(callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900114 return;
115 }
Jinsuk Kimcb802872015-10-13 08:22:09 +0900116 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, Constants.ADDR_TV,
117 callback);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900118 if (action == null) {
119 Slog.w(TAG, "Cannot initiate queryDisplayStatus");
Jinsuk Kimc0c20d02014-07-04 14:34:31 +0900120 invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
Jungshik Jang79c58a42014-06-16 16:45:36 +0900121 return;
122 }
123 addAndStartAction(action);
124 }
125
Jungshik Jang79c58a42014-06-16 16:45:36 +0900126 @Override
Jungshik Janga5b74142014-06-23 18:03:10 +0900127 @ServiceThreadOnly
Jungshik Jang79c58a42014-06-16 16:45:36 +0900128 void onHotplug(int portId, boolean connected) {
Jungshik Janga5b74142014-06-23 18:03:10 +0900129 assertRunOnServiceThread();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900130 mCecMessageCache.flushAll();
Yuncheol Heo89ec14e2014-09-16 15:53:59 +0900131 // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
Jinsuk Kim659c4862015-05-07 12:12:55 +0900132 if (WAKE_ON_HOTPLUG && connected && mService.isPowerStandbyOrTransient()) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900133 mService.wakeUp();
134 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900135 if (!connected) {
136 getWakeLock().release();
137 }
Yuncheol Heo38db6292014-07-01 14:15:14 +0900138 }
139
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900140 @Override
141 @ServiceThreadOnly
142 protected void onStandby(boolean initiatedByCec, int standbyAction) {
143 assertRunOnServiceThread();
Donghyun Cho9ccff512016-04-01 14:31:11 +0900144 if (!mService.isControlEnabled() || initiatedByCec || !mAutoTvOff) {
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900145 return;
146 }
147 switch (standbyAction) {
148 case HdmiControlService.STANDBY_SCREEN_OFF:
Donghyun Cho9ccff512016-04-01 14:31:11 +0900149 mService.sendCecCommand(
150 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_TV));
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900151 break;
152 case HdmiControlService.STANDBY_SHUTDOWN:
153 // ACTION_SHUTDOWN is taken as a signal to power off all the devices.
154 mService.sendCecCommand(
155 HdmiCecMessageBuilder.buildStandby(mAddress, Constants.ADDR_BROADCAST));
156 break;
157 }
158 }
159
160 @Override
161 @ServiceThreadOnly
162 void setAutoDeviceOff(boolean enabled) {
163 assertRunOnServiceThread();
164 mAutoTvOff = enabled;
165 }
166
Yuncheol Heo38db6292014-07-01 14:15:14 +0900167 @ServiceThreadOnly
Amy645c3612018-09-25 10:48:19 -0700168 @VisibleForTesting
Amy9a59d9c2018-08-31 13:47:24 -0700169 void setIsActiveSource(boolean on) {
Yuncheol Heo38db6292014-07-01 14:15:14 +0900170 assertRunOnServiceThread();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900171 mIsActiveSource = on;
172 if (on) {
173 getWakeLock().acquire();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900174 } else {
175 getWakeLock().release();
Jinsuk Kime26d8332015-01-09 08:55:41 +0900176 }
177 }
178
179 @ServiceThreadOnly
Jinsuk Kimbad83932015-02-10 07:10:40 +0900180 private ActiveWakeLock getWakeLock() {
Jinsuk Kime26d8332015-01-09 08:55:41 +0900181 assertRunOnServiceThread();
182 if (mWakeLock == null) {
Jinsuk Kimbad83932015-02-10 07:10:40 +0900183 if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) {
184 mWakeLock = new SystemWakeLock();
185 } else {
186 // Create a dummy lock object that doesn't do anything about wake lock,
187 // hence allows the device to go to sleep even if it's the active source.
188 mWakeLock = new ActiveWakeLock() {
189 @Override
190 public void acquire() { }
191 @Override
192 public void release() { }
193 @Override
194 public boolean isHeld() { return false; }
195 };
196 HdmiLogger.debug("No wakelock is used to keep the display on.");
197 }
Jinsuk Kime26d8332015-01-09 08:55:41 +0900198 }
199 return mWakeLock;
200 }
201
202 @Override
203 protected boolean canGoToStandby() {
204 return !getWakeLock().isHeld();
Yuncheol Heo38db6292014-07-01 14:15:14 +0900205 }
206
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900207 @ServiceThreadOnly
208 protected boolean handleUserControlPressed(HdmiCecMessage message) {
209 assertRunOnServiceThread();
210 wakeUpIfActiveSource();
211 return super.handleUserControlPressed(message);
212 }
213
Yuncheol Heo38db6292014-07-01 14:15:14 +0900214 @Override
Amy34037422018-09-06 13:21:08 -0700215 protected void wakeUpIfActiveSource() {
Jinsuk Kim9b8507c2015-03-24 16:55:01 +0900216 if (!mIsActiveSource) {
217 return;
218 }
219 // Wake up the device if the power is in standby mode, or its screen is off -
220 // which can happen if the device is holding a partial lock.
221 if (mService.isPowerStandbyOrTransient() || !mService.getPowerManager().isScreenOn()) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900222 mService.wakeUp();
223 }
224 }
225
Amy225d55a2018-09-06 11:03:51 -0700226 @Override
227 protected void maySendActiveSource(int dest) {
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900228 if (mIsActiveSource) {
229 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
230 mAddress, mService.getPhysicalAddress()));
Yuncheol Heo8ecb7212014-10-27 17:29:42 +0900231 // Always reports menu-status active to receive RCP.
232 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus(
233 mAddress, dest, Constants.MENU_STATE_ACTIVATED));
Yuncheol Heo64bafd92014-08-11 11:17:54 +0900234 }
235 }
236
Terry Heo795415b2014-10-01 15:03:53 +0900237 @ServiceThreadOnly
238 protected boolean handleSetMenuLanguage(HdmiCecMessage message) {
239 assertRunOnServiceThread();
Donghyun Choa09256c2016-03-11 17:35:37 +0900240 if (!SET_MENU_LANGUAGE) {
241 return false;
242 }
Terry Heo795415b2014-10-01 15:03:53 +0900243
244 try {
245 String iso3Language = new String(message.getParams(), 0, 3, "US-ASCII");
Jinsuk Kim8ea452e2015-07-10 13:01:06 +0900246 Locale currentLocale = mService.getContext().getResources().getConfiguration().locale;
247 if (currentLocale.getISO3Language().equals(iso3Language)) {
248 // Do not switch language if the new language is the same as the current one.
249 // This helps avoid accidental country variant switching from en_US to en_AU
250 // due to the limitation of CEC. See the warning below.
251 return true;
252 }
Terry Heo795415b2014-10-01 15:03:53 +0900253
254 // Don't use Locale.getAvailableLocales() since it returns a locale
255 // which is not available on Settings.
256 final List<LocaleInfo> localeInfos = LocalePicker.getAllAssetLocales(
257 mService.getContext(), false);
258 for (LocaleInfo localeInfo : localeInfos) {
259 if (localeInfo.getLocale().getISO3Language().equals(iso3Language)) {
260 // WARNING: CEC adopts ISO/FDIS-2 for language code, while Android requires
261 // additional country variant to pinpoint the locale. This keeps the right
262 // locale from being chosen. 'eng' in the CEC command, for instance,
263 // will always be mapped to en-AU among other variants like en-US, en-GB,
264 // an en-IN, which may not be the expected one.
265 LocalePicker.updateLocale(localeInfo.getLocale());
266 return true;
267 }
268 }
269 Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language);
270 return false;
271 } catch (UnsupportedEncodingException e) {
Donghyun Choa09256c2016-03-11 17:35:37 +0900272 Slog.w(TAG, "Can't handle <Set Menu Language>", e);
Terry Heo795415b2014-10-01 15:03:53 +0900273 return false;
274 }
275 }
276
Yuncheol Heo38db6292014-07-01 14:15:14 +0900277 @Override
Donghyun Cho7609bc32016-12-02 15:31:52 +0900278 protected int findKeyReceiverAddress() {
279 return Constants.ADDR_TV;
280 }
281
282 @Override
Yuncheol Heo38db6292014-07-01 14:15:14 +0900283 @ServiceThreadOnly
Jungshik Jang4fc1d102014-07-09 19:24:50 +0900284 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
285 super.disableDevice(initiatedByCec, callback);
286
Yuncheol Heo38db6292014-07-01 14:15:14 +0900287 assertRunOnServiceThread();
288 if (!initiatedByCec && mIsActiveSource) {
289 mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
290 mAddress, mService.getPhysicalAddress()));
291 }
Amy9a59d9c2018-08-31 13:47:24 -0700292 setIsActiveSource(false);
Yuncheol Heo38db6292014-07-01 14:15:14 +0900293 checkIfPendingActionsCleared();
Jungshik Jang79c58a42014-06-16 16:45:36 +0900294 }
Terry Heo959d2db2014-08-28 16:45:41 +0900295
Amyaefab642018-08-22 19:10:14 -0700296 private void routeToPort(int portId) {
297 // TODO(AMYJOJO): route to specific input of the port
298 mLocalActivePath = portId;
299 }
300
301 @VisibleForTesting
302 protected int getLocalActivePath() {
303 return mLocalActivePath;
304 }
305
Terry Heo959d2db2014-08-28 16:45:41 +0900306 @Override
307 protected void dump(final IndentingPrintWriter pw) {
308 super.dump(pw);
309 pw.println("mIsActiveSource: " + mIsActiveSource);
Jinsuk Kime6e8f3d2015-05-11 14:17:04 +0900310 pw.println("mAutoTvOff:" + mAutoTvOff);
Terry Heo959d2db2014-08-28 16:45:41 +0900311 }
Jinsuk Kimbad83932015-02-10 07:10:40 +0900312
313 // Wrapper interface over PowerManager.WakeLock
314 private interface ActiveWakeLock {
315 void acquire();
316 void release();
317 boolean isHeld();
318 }
319
320 private class SystemWakeLock implements ActiveWakeLock {
321 private final WakeLock mWakeLock;
322 public SystemWakeLock() {
323 mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
324 mWakeLock.setReferenceCounted(false);
325 }
326
327 @Override
328 public void acquire() {
329 mWakeLock.acquire();
330 HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource);
331 }
332
333 @Override
334 public void release() {
335 mWakeLock.release();
336 HdmiLogger.debug("Wake lock released");
337 }
338
339 @Override
340 public boolean isHeld() {
341 return mWakeLock.isHeld();
342 }
343 }
Jinsuk Kim84659ed2014-10-06 23:48:19 +0000344}