1 /*
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 
17 package com.android.server.hdmi;
18 
19 import android.annotation.CallSuper;
20 import android.content.ActivityNotFoundException;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.hardware.hdmi.HdmiControlManager;
25 import android.hardware.hdmi.HdmiDeviceInfo;
26 import android.hardware.hdmi.IHdmiControlCallback;
27 import android.hardware.tv.cec.V1_0.SendMessageResult;
28 import android.os.Binder;
29 import android.os.Handler;
30 import android.os.PowerManager;
31 import android.os.PowerManager.WakeLock;
32 import android.os.SystemProperties;
33 import android.sysprop.HdmiProperties;
34 import android.util.Slog;
35 
36 import com.android.internal.annotations.VisibleForTesting;
37 import com.android.internal.app.LocalePicker;
38 import com.android.internal.app.LocalePicker.LocaleInfo;
39 import com.android.internal.util.IndentingPrintWriter;
40 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
41 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
42 
43 import java.io.UnsupportedEncodingException;
44 import java.util.List;
45 import java.util.Locale;
46 
47 /**
48  * Represent a logical device of type Playback residing in Android system.
49  */
50 public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
51     private static final String TAG = "HdmiCecLocalDevicePlayback";
52 
53     // How long to wait after hotplug out before possibly going to Standby.
54     @VisibleForTesting
55     static final long STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS = 30_000;
56 
57     // Used to keep the device awake while it is the active source. For devices that
58     // cannot wake up via CEC commands, this address the inconvenience of having to
59     // turn them on. True by default, and can be disabled (i.e. device can go to sleep
60     // in active device status) by explicitly setting the system property
61     // persist.sys.hdmi.keep_awake to false.
62     // Lazily initialized - should call getWakeLock() to get the instance.
63     private ActiveWakeLock mWakeLock;
64 
65     // Handler for queueing a delayed Standby runnable after hotplug out.
66     private Handler mDelayedStandbyHandler;
67 
68     // Determines what action should be taken upon receiving Routing Control messages.
69     @VisibleForTesting
70     protected HdmiProperties.playback_device_action_on_routing_control_values
71             mPlaybackDeviceActionOnRoutingControl = HdmiProperties
72                     .playback_device_action_on_routing_control()
73                     .orElse(HdmiProperties.playback_device_action_on_routing_control_values.NONE);
74 
HdmiCecLocalDevicePlayback(HdmiControlService service)75     HdmiCecLocalDevicePlayback(HdmiControlService service) {
76         super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
77 
78         mDelayedStandbyHandler = new Handler(service.getServiceLooper());
79         mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
80     }
81 
82     @Override
83     @ServiceThreadOnly
onAddressAllocated(int logicalAddress, int reason)84     protected void onAddressAllocated(int logicalAddress, int reason) {
85         assertRunOnServiceThread();
86         if (reason == mService.INITIATED_BY_ENABLE_CEC) {
87             mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
88                     getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST,
89                     "HdmiCecLocalDevicePlayback#onAddressAllocated()");
90         }
91         mService.sendCecCommand(
92                 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
93                         getDeviceInfo().getLogicalAddress(),
94                         mService.getPhysicalAddress(),
95                         mDeviceType));
96         mService.sendCecCommand(
97                 HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
98                         getDeviceInfo().getLogicalAddress(), mService.getVendorId()));
99         // Actively send out an OSD name to the TV to update the TV panel in case the TV
100         // does not query the OSD name on time. This is not a required behavior by the spec.
101         // It is used for some TVs that need the OSD name update but don't query it themselves.
102         buildAndSendSetOsdName(Constants.ADDR_TV);
103         if (mService.audioSystem() == null) {
104             // If current device is not a functional audio system device,
105             // send message to potential audio system device in the system to get the system
106             // audio mode status. If no response, set to false.
107             mService.sendCecCommand(
108                     HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
109                             getDeviceInfo().getLogicalAddress(), Constants.ADDR_AUDIO_SYSTEM),
110                     new SendMessageCallback() {
111                         @Override
112                         public void onSendCompleted(int error) {
113                             if (error != SendMessageResult.SUCCESS) {
114                                 HdmiLogger.debug(
115                                         "AVR did not respond to <Give System Audio Mode Status>");
116                                 mService.setSystemAudioActivated(false);
117                             }
118                         }
119                     });
120         }
121         launchDeviceDiscovery();
122         startQueuedActions();
123     }
124 
125     @ServiceThreadOnly
launchDeviceDiscovery()126     private void launchDeviceDiscovery() {
127         assertRunOnServiceThread();
128         clearDeviceInfoList();
129         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
130                 new DeviceDiscoveryAction.DeviceDiscoveryCallback() {
131                     @Override
132                     public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
133                         for (HdmiDeviceInfo info : deviceInfos) {
134                             mService.getHdmiCecNetwork().addCecDevice(info);
135                         }
136 
137                         // Since we removed all devices when it starts and device discovery action
138                         // does not poll local devices, we should put device info of local device
139                         // manually here.
140                         for (HdmiCecLocalDevice device : mService.getAllCecLocalDevices()) {
141                             mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
142                         }
143 
144                         List<HotplugDetectionAction> hotplugActions =
145                                 getActions(HotplugDetectionAction.class);
146                         if (hotplugActions.isEmpty()) {
147                             addAndStartAction(
148                                     new HotplugDetectionAction(HdmiCecLocalDevicePlayback.this));
149                         }
150                     }
151                 });
152         addAndStartAction(action);
153     }
154 
155     @Override
156     @ServiceThreadOnly
getPreferredAddress()157     protected int getPreferredAddress() {
158         assertRunOnServiceThread();
159         return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
160                 Constants.ADDR_UNREGISTERED);
161     }
162 
163     @Override
164     @ServiceThreadOnly
setPreferredAddress(int addr)165     protected void setPreferredAddress(int addr) {
166         assertRunOnServiceThread();
167         mService.writeStringSystemProperty(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
168                 String.valueOf(addr));
169     }
170 
171     /**
172      * Performs the action 'device select' or 'one touch play' initiated by a Playback device.
173      *
174      * @param id id of HDMI device to select
175      * @param callback callback object to report the result with
176      */
177     @ServiceThreadOnly
deviceSelect(int id, IHdmiControlCallback callback)178     void deviceSelect(int id, IHdmiControlCallback callback) {
179         assertRunOnServiceThread();
180         if (id == getDeviceInfo().getId()) {
181             mService.oneTouchPlay(callback);
182             return;
183         }
184         HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id);
185         if (targetDevice == null) {
186             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
187             return;
188         }
189         int targetAddress = targetDevice.getLogicalAddress();
190         if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) {
191             return;
192         }
193         if (!mService.isCecControlEnabled()) {
194             setActiveSource(targetDevice, "HdmiCecLocalDevicePlayback#deviceSelect()");
195             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
196             return;
197         }
198         removeAction(DeviceSelectActionFromPlayback.class);
199         addAndStartAction(new DeviceSelectActionFromPlayback(this, targetDevice, callback));
200     }
201 
202     @Override
203     @ServiceThreadOnly
onHotplug(int portId, boolean connected)204     void onHotplug(int portId, boolean connected) {
205         assertRunOnServiceThread();
206         mCecMessageCache.flushAll();
207 
208         if (connected) {
209             mDelayedStandbyHandler.removeCallbacksAndMessages(null);
210         } else {
211             // We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3
212             getWakeLock().release();
213             mService.getHdmiCecNetwork().removeDevicesConnectedToPort(portId);
214 
215             mDelayedStandbyHandler.removeCallbacksAndMessages(null);
216             mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(),
217                     STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
218         }
219     }
220 
221     /**
222      * Runnable for going to Standby if the device has been inactive for a certain amount of time.
223      * Posts a new instance of itself as a delayed message if the device was active.
224      */
225     private class DelayedStandbyRunnable implements Runnable {
226         @Override
run()227         public void run() {
228             if (mService.getPowerManagerInternal().wasDeviceIdleFor(
229                     STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS)) {
230                 mService.standby();
231             } else {
232                 mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(),
233                         STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
234             }
235         }
236     }
237 
238     @Override
239     @ServiceThreadOnly
onStandby(boolean initiatedByCec, int standbyAction)240     protected void onStandby(boolean initiatedByCec, int standbyAction) {
241         assertRunOnServiceThread();
242         if (!mService.isCecControlEnabled()) {
243             return;
244         }
245         boolean wasActiveSource = isActiveSource();
246         // Invalidate the internal active source record when going to standby
247         mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS,
248                 "HdmiCecLocalDevicePlayback#onStandby()");
249         if (!wasActiveSource) {
250             return;
251         }
252         if (initiatedByCec) {
253             mService.sendCecCommand(
254                     HdmiCecMessageBuilder.buildInactiveSource(
255                             getDeviceInfo().getLogicalAddress(), mService.getPhysicalAddress()));
256             return;
257         }
258         switch (standbyAction) {
259             case HdmiControlService.STANDBY_SCREEN_OFF:
260                 // Get latest setting value
261                 @HdmiControlManager.PowerControlMode
262                 String powerControlMode = mService.getHdmiCecConfig().getStringValue(
263                         HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE);
264                 switch (powerControlMode) {
265                     case HdmiControlManager.POWER_CONTROL_MODE_TV:
266                         mService.sendCecCommand(
267                                 HdmiCecMessageBuilder.buildStandby(
268                                         getDeviceInfo().getLogicalAddress(), Constants.ADDR_TV));
269                         break;
270                     case HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM:
271                         mService.sendCecCommand(
272                                 HdmiCecMessageBuilder.buildStandby(
273                                         getDeviceInfo().getLogicalAddress(), Constants.ADDR_TV));
274                         mService.sendCecCommand(
275                                 HdmiCecMessageBuilder.buildStandby(
276                                         getDeviceInfo().getLogicalAddress(),
277                                         Constants.ADDR_AUDIO_SYSTEM));
278                         break;
279                     case HdmiControlManager.POWER_CONTROL_MODE_BROADCAST:
280                         mService.sendCecCommand(
281                                 HdmiCecMessageBuilder.buildStandby(
282                                         getDeviceInfo().getLogicalAddress(),
283                                         Constants.ADDR_BROADCAST));
284                         break;
285                     case HdmiControlManager.POWER_CONTROL_MODE_NONE:
286                         mService.sendCecCommand(
287                                 HdmiCecMessageBuilder.buildInactiveSource(
288                                         getDeviceInfo().getLogicalAddress(),
289                                         mService.getPhysicalAddress()));
290                         break;
291                 }
292                 break;
293             case HdmiControlService.STANDBY_SHUTDOWN:
294                 // ACTION_SHUTDOWN is taken as a signal to power off all the devices.
295                 mService.sendCecCommand(
296                         HdmiCecMessageBuilder.buildStandby(
297                                 getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST));
298                 break;
299         }
300     }
301 
302     @Override
303     @ServiceThreadOnly
onInitializeCecComplete(int initiatedBy)304     protected void onInitializeCecComplete(int initiatedBy) {
305         if (initiatedBy != HdmiControlService.INITIATED_BY_SCREEN_ON) {
306             return;
307         }
308         @HdmiControlManager.PowerControlMode
309         String powerControlMode = mService.getHdmiCecConfig().getStringValue(
310                 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE);
311         if (powerControlMode.equals(HdmiControlManager.POWER_CONTROL_MODE_NONE)) {
312             return;
313         }
314         oneTouchPlay(new IHdmiControlCallback.Stub() {
315             @Override
316             public void onComplete(int result) {
317                 if (result != HdmiControlManager.RESULT_SUCCESS) {
318                     Slog.w(TAG, "Failed to complete One Touch Play. result=" + result);
319                 }
320             }
321         });
322     }
323 
324     @Override
325     @CallSuper
326     @ServiceThreadOnly
327     @VisibleForTesting
setActiveSource(int logicalAddress, int physicalAddress, String caller)328     protected void setActiveSource(int logicalAddress, int physicalAddress, String caller) {
329         assertRunOnServiceThread();
330         super.setActiveSource(logicalAddress, physicalAddress, caller);
331         if (isActiveSource()) {
332             getWakeLock().acquire();
333         } else {
334             getWakeLock().release();
335         }
336     }
337 
338     @ServiceThreadOnly
getWakeLock()339     private ActiveWakeLock getWakeLock() {
340         assertRunOnServiceThread();
341         if (mWakeLock == null) {
342             if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) {
343                 mWakeLock = new SystemWakeLock();
344             } else {
345                 // Create a stub lock object that doesn't do anything about wake lock,
346                 // hence allows the device to go to sleep even if it's the active source.
347                 mWakeLock = new ActiveWakeLock() {
348                     @Override
349                     public void acquire() { }
350                     @Override
351                     public void release() { }
352                     @Override
353                     public boolean isHeld() { return false; }
354                 };
355                 HdmiLogger.debug("No wakelock is used to keep the display on.");
356             }
357         }
358         return mWakeLock;
359     }
360 
361     @Override
canGoToStandby()362     protected boolean canGoToStandby() {
363         return !getWakeLock().isHeld();
364     }
365 
366     @Override
367     @ServiceThreadOnly
onActiveSourceLost()368     protected void onActiveSourceLost() {
369         assertRunOnServiceThread();
370         mService.pauseActiveMediaSessions();
371         switch (mService.getHdmiCecConfig().getStringValue(
372                     HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) {
373             case HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW:
374                 mService.standby();
375                 return;
376             case HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE:
377                 return;
378         }
379     }
380 
381     @ServiceThreadOnly
382     @Constants.HandleMessageResult
handleUserControlPressed(HdmiCecMessage message)383     protected int handleUserControlPressed(HdmiCecMessage message) {
384         assertRunOnServiceThread();
385         wakeUpIfActiveSource();
386         return super.handleUserControlPressed(message);
387     }
388 
389     @ServiceThreadOnly
390     @Constants.HandleMessageResult
handleSetMenuLanguage(HdmiCecMessage message)391     protected int handleSetMenuLanguage(HdmiCecMessage message) {
392         assertRunOnServiceThread();
393         if (mService.getHdmiCecConfig().getIntValue(
394                 HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE)
395                     == HdmiControlManager.SET_MENU_LANGUAGE_DISABLED) {
396             return Constants.ABORT_UNRECOGNIZED_OPCODE;
397         }
398 
399         try {
400             String iso3Language = new String(message.getParams(), 0, 3, "US-ASCII");
401             Locale currentLocale = mService.getContext().getResources().getConfiguration().locale;
402             String curIso3Language = mService.localeToMenuLanguage(currentLocale);
403             HdmiLogger.debug("handleSetMenuLanguage " + iso3Language + " cur:" + curIso3Language);
404             if (curIso3Language.equals(iso3Language)) {
405                 // Do not switch language if the new language is the same as the current one.
406                 // This helps avoid accidental country variant switching from en_US to en_AU
407                 // due to the limitation of CEC. See the warning below.
408                 return Constants.HANDLED;
409             }
410 
411             // Don't use Locale.getAvailableLocales() since it returns a locale
412             // which is not available on Settings.
413             final List<LocaleInfo> localeInfos = LocalePicker.getAllAssetLocales(
414                     mService.getContext(), false);
415             for (LocaleInfo localeInfo : localeInfos) {
416                 if (mService.localeToMenuLanguage(localeInfo.getLocale()).equals(iso3Language)) {
417                     // WARNING: CEC adopts ISO/FDIS-2 for language code, while Android requires
418                     // additional country variant to pinpoint the locale. This keeps the right
419                     // locale from being chosen. 'eng' in the CEC command, for instance,
420                     // will always be mapped to en-AU among other variants like en-US, en-GB,
421                     // an en-IN, which may not be the expected one.
422                     startSetMenuLanguageActivity(localeInfo.getLocale());
423                     return Constants.HANDLED;
424                 }
425             }
426             Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language);
427             return Constants.ABORT_INVALID_OPERAND;
428         } catch (UnsupportedEncodingException e) {
429             Slog.w(TAG, "Can't handle <Set Menu Language>", e);
430             return Constants.ABORT_INVALID_OPERAND;
431         }
432     }
433 
startSetMenuLanguageActivity(Locale locale)434     private void startSetMenuLanguageActivity(Locale locale) {
435         final long identity = Binder.clearCallingIdentity();
436         try {
437             Context context = mService.getContext();
438             Intent intent = new Intent();
439             intent.putExtra(HdmiControlManager.EXTRA_LOCALE, locale.toLanguageTag());
440             intent.setComponent(
441                     ComponentName.unflattenFromString(context.getResources().getString(
442                             com.android.internal.R.string.config_hdmiCecSetMenuLanguageActivity)));
443             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
444             context.startActivityAsUser(intent, context.getUser());
445         } catch (ActivityNotFoundException e) {
446             Slog.e(TAG, "unable to start HdmiCecSetMenuLanguageActivity");
447         } finally {
448             Binder.restoreCallingIdentity(identity);
449         }
450     }
451 
452     @Override
453     @Constants.HandleMessageResult
handleSetSystemAudioMode(HdmiCecMessage message)454     protected int handleSetSystemAudioMode(HdmiCecMessage message) {
455         // System Audio Mode only turns on/off when Audio System broadcasts on/off message.
456         // For device with type 4 and 5, it can set system audio mode on/off
457         // when there is another audio system device connected into the system first.
458         if (message.getDestination() != Constants.ADDR_BROADCAST
459                 || message.getSource() != Constants.ADDR_AUDIO_SYSTEM
460                 || mService.audioSystem() != null) {
461             return Constants.HANDLED;
462         }
463         boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message);
464         if (mService.isSystemAudioActivated() != setSystemAudioModeOn) {
465             mService.setSystemAudioActivated(setSystemAudioModeOn);
466         }
467         return Constants.HANDLED;
468     }
469 
470     @Override
471     @Constants.HandleMessageResult
handleSystemAudioModeStatus(HdmiCecMessage message)472     protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
473         // Only directly addressed System Audio Mode Status message can change internal
474         // system audio mode status.
475         if (message.getDestination() == getDeviceInfo().getLogicalAddress()
476                 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM) {
477             boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message);
478             if (mService.isSystemAudioActivated() != setSystemAudioModeOn) {
479                 mService.setSystemAudioActivated(setSystemAudioModeOn);
480             }
481         }
482         return Constants.HANDLED;
483     }
484 
485     @Override
486     @ServiceThreadOnly
487     @Constants.HandleMessageResult
handleRoutingChange(HdmiCecMessage message)488     protected int handleRoutingChange(HdmiCecMessage message) {
489         assertRunOnServiceThread();
490         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2);
491         handleRoutingChangeAndInformation(physicalAddress, message);
492         return Constants.HANDLED;
493     }
494 
495     @Override
496     @ServiceThreadOnly
497     @Constants.HandleMessageResult
handleRoutingInformation(HdmiCecMessage message)498     protected int handleRoutingInformation(HdmiCecMessage message) {
499         assertRunOnServiceThread();
500         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
501         handleRoutingChangeAndInformation(physicalAddress, message);
502         return Constants.HANDLED;
503     }
504 
505     @Override
506     @ServiceThreadOnly
handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message)507     protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
508         assertRunOnServiceThread();
509         if (physicalAddress != mService.getPhysicalAddress()) {
510             setActiveSource(physicalAddress,
511                     "HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()");
512             return;
513         }
514         if (!isActiveSource()) {
515             // If routing is changed to the device while Active Source, don't invalidate the
516             // Active Source
517             setActiveSource(physicalAddress,
518                     "HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()");
519         }
520         switch (mPlaybackDeviceActionOnRoutingControl) {
521             case WAKE_UP_AND_SEND_ACTIVE_SOURCE:
522                 setAndBroadcastActiveSource(message, physicalAddress,
523                         "HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()");
524                 break;
525             case WAKE_UP_ONLY:
526                 mService.wakeUp();
527                 break;
528             case NONE:
529                 break;
530         }
531     }
532 
533     /**
534      * Called after logical address allocation is finished, allowing a local device to react to
535      * messages in the buffer before they are processed. This method may be used to cancel deferred
536      * actions.
537      */
538     @Override
preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages)539     protected void preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages) {
540         for (HdmiCecMessage message: bufferedMessages) {
541             // Prevent the device from broadcasting <Active Source> message if the active path
542             // changed during address allocation.
543             if (message.getOpcode() == Constants.MESSAGE_ROUTING_CHANGE
544                     || message.getOpcode() == Constants.MESSAGE_SET_STREAM_PATH
545                     || message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) {
546                 removeAction(ActiveSourceAction.class);
547                 removeAction(OneTouchPlayAction.class);
548                 return;
549             }
550         }
551     }
552 
553     @Override
findKeyReceiverAddress()554     protected int findKeyReceiverAddress() {
555         return Constants.ADDR_TV;
556     }
557 
558     @Override
findAudioReceiverAddress()559     protected int findAudioReceiverAddress() {
560         if (mService.isSystemAudioActivated()) {
561             return Constants.ADDR_AUDIO_SYSTEM;
562         }
563         return Constants.ADDR_TV;
564     }
565 
566     @Override
567     @ServiceThreadOnly
disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback)568     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
569         assertRunOnServiceThread();
570         removeAction(DeviceDiscoveryAction.class);
571         removeAction(HotplugDetectionAction.class);
572         removeAction(NewDeviceAction.class);
573         super.disableDevice(initiatedByCec, callback);
574         clearDeviceInfoList();
575         checkIfPendingActionsCleared();
576     }
577 
578     @Override
dump(final IndentingPrintWriter pw)579     protected void dump(final IndentingPrintWriter pw) {
580         super.dump(pw);
581         pw.println("isActiveSource(): " + isActiveSource());
582     }
583 
584     // Wrapper interface over PowerManager.WakeLock
585     private interface ActiveWakeLock {
acquire()586         void acquire();
release()587         void release();
isHeld()588         boolean isHeld();
589     }
590 
591     private class SystemWakeLock implements ActiveWakeLock {
592         private final WakeLock mWakeLock;
SystemWakeLock()593         public SystemWakeLock() {
594             mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
595             mWakeLock.setReferenceCounted(false);
596         }
597 
598         @Override
acquire()599         public void acquire() {
600             mWakeLock.acquire();
601             HdmiLogger.debug("active source: %b. Wake lock acquired", isActiveSource());
602         }
603 
604         @Override
release()605         public void release() {
606             mWakeLock.release();
607             HdmiLogger.debug("Wake lock released");
608         }
609 
610         @Override
isHeld()611         public boolean isHeld() {
612             return mWakeLock.isHeld();
613         }
614     }
615 }
616