• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.SystemProperties;
32 import android.sysprop.HdmiProperties;
33 import android.util.Slog;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.internal.app.LocalePicker;
37 import com.android.internal.app.LocalePicker.LocaleInfo;
38 import com.android.internal.util.IndentingPrintWriter;
39 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
40 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
41 
42 import java.io.UnsupportedEncodingException;
43 import java.util.List;
44 import java.util.Locale;
45 
46 /**
47  * Represent a logical device of type Playback residing in Android system.
48  */
49 public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
50     private static final String TAG = "HdmiCecLocalDevicePlayback";
51 
52     // How long to wait after hotplug out before possibly going to Standby.
53     @VisibleForTesting
54     static final long STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS = 30_000;
55 
56     // How long to wait on active source lost before possibly going to Standby.
57     @VisibleForTesting
58     static final long STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS = 30_000;
59 
60     // How long to wait after losing active source, before launching the pop-up that allows the user
61     // to keep the device as the current active source.
62     // We do this to prevent an unnecessary pop-up from being displayed when we lose and regain
63     // active source within this timeout.
64     static final long POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS = 5_000;
65 
66     // Used to keep the device awake while it is the active source. For devices that
67     // cannot wake up via CEC commands, this address the inconvenience of having to
68     // turn them on. True by default, and can be disabled (i.e. device can go to sleep
69     // in active device status) by explicitly setting the system property
70     // persist.sys.hdmi.keep_awake to false.
71     // Lazily initialized - should call getWakeLock() to get the instance.
72     private ActiveWakeLock mWakeLock;
73 
74     // Handler for queueing a delayed Standby runnable after hotplug out.
75     private Handler mDelayedStandbyHandler;
76 
77     // Handler for queueing a delayed Standby runnable after active source lost and after the pop-up
78     // on active source lost was displayed.
79     Handler mDelayedStandbyOnActiveSourceLostHandler;
80 
81     // Handler for queueing a delayed runnable that triggers a pop-up notification on active source
82     // lost.
83     private Handler mDelayedPopupOnActiveSourceLostHandler;
84 
85     private boolean mIsActiveSourceLostPopupLaunched;
86 
87     // Determines what action should be taken upon receiving Routing Control messages.
88     @VisibleForTesting
89     protected HdmiProperties.playback_device_action_on_routing_control_values
90             mPlaybackDeviceActionOnRoutingControl = HdmiProperties
91                     .playback_device_action_on_routing_control()
92                     .orElse(HdmiProperties.playback_device_action_on_routing_control_values.NONE);
93 
HdmiCecLocalDevicePlayback(HdmiControlService service)94     HdmiCecLocalDevicePlayback(HdmiControlService service) {
95         super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
96 
97         mDelayedStandbyHandler = new Handler(service.getServiceLooper());
98         mDelayedStandbyOnActiveSourceLostHandler = new Handler(service.getServiceLooper());
99         mDelayedPopupOnActiveSourceLostHandler = new Handler(service.getServiceLooper());
100         mStandbyHandler = new HdmiCecStandbyModeHandler(service, this);
101         mIsActiveSourceLostPopupLaunched = false;
102     }
103 
104     @Override
105     @ServiceThreadOnly
onAddressAllocated(int logicalAddress, int reason)106     protected void onAddressAllocated(int logicalAddress, int reason) {
107         assertRunOnServiceThread();
108         if (reason == mService.INITIATED_BY_ENABLE_CEC) {
109             mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
110                     getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST,
111                     "HdmiCecLocalDevicePlayback#onAddressAllocated()");
112         }
113         mService.sendCecCommand(
114                 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
115                         getDeviceInfo().getLogicalAddress(),
116                         mService.getPhysicalAddress(),
117                         mDeviceType));
118         mService.sendCecCommand(
119                 HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
120                         getDeviceInfo().getLogicalAddress(), mService.getVendorId()));
121         // Actively send out an OSD name to the TV to update the TV panel in case the TV
122         // does not query the OSD name on time. This is not a required behavior by the spec.
123         // It is used for some TVs that need the OSD name update but don't query it themselves.
124         buildAndSendSetOsdName(Constants.ADDR_TV);
125         if (mService.audioSystem() == null) {
126             // If current device is not a functional audio system device,
127             // send message to potential audio system device in the system to get the system
128             // audio mode status. If no response, set to false.
129             mService.sendCecCommand(
130                     HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(
131                             getDeviceInfo().getLogicalAddress(), Constants.ADDR_AUDIO_SYSTEM),
132                     new SendMessageCallback() {
133                         @Override
134                         public void onSendCompleted(int error) {
135                             // In consideration of occasional transmission failures.
136                             if (error == SendMessageResult.NACK) {
137                                 HdmiLogger.debug(
138                                         "AVR did not respond to <Give System Audio Mode Status>");
139                                 mService.setSystemAudioActivated(false);
140                             }
141                         }
142                     });
143         }
144         launchDeviceDiscovery();
145         startQueuedActions();
146     }
147 
148     @ServiceThreadOnly
launchDeviceDiscovery()149     private void launchDeviceDiscovery() {
150         assertRunOnServiceThread();
151         clearDeviceInfoList();
152         DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
153                 new DeviceDiscoveryAction.DeviceDiscoveryCallback() {
154                     @Override
155                     public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
156                         for (HdmiDeviceInfo info : deviceInfos) {
157                             mService.getHdmiCecNetwork().addCecDevice(info);
158                         }
159 
160                         // Since we removed all devices when it starts and device discovery
161                         // action does not poll local devices, we should put device info of
162                         // local device manually here.
163                         for (HdmiCecLocalDevice device : mService.getAllCecLocalDevices()) {
164                             mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo());
165                         }
166 
167                         if (!hasAction(HotplugDetectionAction.class)) {
168                             addAndStartAction(
169                                     new HotplugDetectionAction(
170                                             HdmiCecLocalDevicePlayback.this));
171                         }
172 
173                         if (mService.isHdmiControlEnhancedBehaviorFlagEnabled()) {
174                             if (!hasAction(PowerStatusMonitorActionFromPlayback.class)) {
175                                 addAndStartAction(
176                                         new PowerStatusMonitorActionFromPlayback(
177                                                 HdmiCecLocalDevicePlayback.this));
178                             }
179                         }
180                     }
181                 });
182         addAndStartAction(action, true);
183     }
184 
185     @Override
186     @ServiceThreadOnly
getPreferredAddress()187     protected int getPreferredAddress() {
188         assertRunOnServiceThread();
189         return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
190                 Constants.ADDR_UNREGISTERED);
191     }
192 
193     @Override
194     @ServiceThreadOnly
setPreferredAddress(int addr)195     protected void setPreferredAddress(int addr) {
196         assertRunOnServiceThread();
197         mService.writeStringSystemProperty(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
198                 String.valueOf(addr));
199     }
200 
201     /**
202      * Performs the action 'device select' or 'one touch play' initiated by a Playback device.
203      *
204      * @param id id of HDMI device to select
205      * @param callback callback object to report the result with
206      */
207     @ServiceThreadOnly
deviceSelect(int id, IHdmiControlCallback callback)208     void deviceSelect(int id, IHdmiControlCallback callback) {
209         assertRunOnServiceThread();
210         if (id == getDeviceInfo().getId()) {
211             mService.oneTouchPlay(callback);
212             return;
213         }
214         HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id);
215         if (targetDevice == null) {
216             invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
217             return;
218         }
219         int targetAddress = targetDevice.getLogicalAddress();
220         if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) {
221             return;
222         }
223         if (!mService.isCecControlEnabled()) {
224             setActiveSource(targetDevice, "HdmiCecLocalDevicePlayback#deviceSelect()");
225             invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
226             return;
227         }
228         List<DeviceSelectActionFromPlayback> actions = getActions(
229                 DeviceSelectActionFromPlayback.class);
230         if (!actions.isEmpty()) {
231             DeviceSelectActionFromPlayback action = actions.get(0);
232             if (action.getTargetAddress() == targetDevice.getLogicalAddress()) {
233                 return;
234             }
235         }
236         addAndStartAction(new DeviceSelectActionFromPlayback(this, targetDevice, callback),
237                 true);
238     }
239 
240     @Override
241     @ServiceThreadOnly
onHotplug(int portId, boolean connected)242     void onHotplug(int portId, boolean connected) {
243         assertRunOnServiceThread();
244         mCecMessageCache.flushAll();
245 
246         if (connected) {
247             mDelayedStandbyHandler.removeCallbacksAndMessages(null);
248         } else {
249             // We'll not invalidate the active source on the hotplug event to pass CETC 11.2.2-2 ~ 3
250             getWakeLock().release();
251             mService.getHdmiCecNetwork().removeDevicesConnectedToPort(portId);
252 
253             mDelayedStandbyHandler.removeCallbacksAndMessages(null);
254             mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(),
255                     STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
256         }
257     }
258 
259     /**
260      * Runnable for going to Standby if the device has been inactive for a certain amount of time.
261      * Posts a new instance of itself as a delayed message if the device was active.
262      */
263     private class DelayedStandbyRunnable implements Runnable {
264         @Override
run()265         public void run() {
266             if (mService.getPowerManagerInternal().wasDeviceIdleFor(
267                     STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS)) {
268                 mService.standby();
269             } else {
270                 mDelayedStandbyHandler.postDelayed(new DelayedStandbyRunnable(),
271                         STANDBY_AFTER_HOTPLUG_OUT_DELAY_MS);
272             }
273         }
274     }
275 
276     private class DelayedStandbyOnActiveSourceLostRunnable implements Runnable {
277         @Override
run()278         public void run() {
279             if (!isActiveSource()) {
280                 mService.standby();
281                 mIsActiveSourceLostPopupLaunched = false;
282             }
283         }
284     }
285 
286     @ServiceThreadOnly
dismissUiOnActiveSourceStatusRecovered()287     void dismissUiOnActiveSourceStatusRecovered() {
288         assertRunOnServiceThread();
289         Intent intent = new Intent(HdmiControlManager.ACTION_ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI);
290         mIsActiveSourceLostPopupLaunched = false;
291         mService.sendBroadcastAsUser(intent);
292     }
293 
294     @Override
295     @ServiceThreadOnly
onStandby(boolean initiatedByCec, int standbyAction, StandbyCompletedCallback callback)296     protected void onStandby(boolean initiatedByCec, int standbyAction,
297             StandbyCompletedCallback callback) {
298         assertRunOnServiceThread();
299         if (!mService.isCecControlEnabled()) {
300             invokeStandbyCompletedCallback(callback);
301             return;
302         }
303         boolean wasActiveSource = isActiveSource();
304         // Invalidate the internal active source record when going to standby
305         mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS,
306                 "HdmiCecLocalDevicePlayback#onStandby()");
307         if (!wasActiveSource) {
308             invokeStandbyCompletedCallback(callback);
309             return;
310         }
311         SendMessageCallback sendMessageCallback = new SendMessageCallback() {
312             @Override
313             public void onSendCompleted(int error) {
314                 invokeStandbyCompletedCallback(callback);
315             }
316         };
317         if (initiatedByCec) {
318             mService.sendCecCommand(
319                     HdmiCecMessageBuilder.buildInactiveSource(
320                             getDeviceInfo().getLogicalAddress(), mService.getPhysicalAddress()),
321                     sendMessageCallback);
322             return;
323         }
324         switch (standbyAction) {
325             case HdmiControlService.STANDBY_SCREEN_OFF:
326                 // Get latest setting value
327                 @HdmiControlManager.PowerControlMode
328                 String powerControlMode = mService.getHdmiCecConfig().getStringValue(
329                         HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE);
330                 switch (powerControlMode) {
331                     case HdmiControlManager.POWER_CONTROL_MODE_TV:
332                         mService.sendCecCommand(
333                                 HdmiCecMessageBuilder.buildStandby(
334                                         getDeviceInfo().getLogicalAddress(), Constants.ADDR_TV),
335                                 sendMessageCallback);
336                         break;
337                     case HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM:
338                         mService.sendCecCommand(
339                                 HdmiCecMessageBuilder.buildStandby(
340                                         getDeviceInfo().getLogicalAddress(), Constants.ADDR_TV));
341                         mService.sendCecCommand(
342                                 HdmiCecMessageBuilder.buildStandby(
343                                         getDeviceInfo().getLogicalAddress(),
344                                         Constants.ADDR_AUDIO_SYSTEM), sendMessageCallback);
345                         break;
346                     case HdmiControlManager.POWER_CONTROL_MODE_BROADCAST:
347                         mService.sendCecCommand(
348                                 HdmiCecMessageBuilder.buildStandby(
349                                         getDeviceInfo().getLogicalAddress(),
350                                         Constants.ADDR_BROADCAST), sendMessageCallback);
351                         break;
352                     case HdmiControlManager.POWER_CONTROL_MODE_NONE:
353                         mService.sendCecCommand(
354                                 HdmiCecMessageBuilder.buildInactiveSource(
355                                         getDeviceInfo().getLogicalAddress(),
356                                         mService.getPhysicalAddress()), sendMessageCallback);
357                         break;
358                 }
359                 break;
360             case HdmiControlService.STANDBY_SHUTDOWN:
361                 // ACTION_SHUTDOWN is taken as a signal to power off all the devices.
362                 mService.sendCecCommand(
363                         HdmiCecMessageBuilder.buildStandby(
364                                 getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST),
365                         sendMessageCallback);
366                 break;
367         }
368     }
369 
370     @Override
371     @ServiceThreadOnly
onInitializeCecComplete(int initiatedBy)372     protected void onInitializeCecComplete(int initiatedBy) {
373         if (initiatedBy != HdmiControlService.INITIATED_BY_SCREEN_ON) {
374             return;
375         }
376         @HdmiControlManager.PowerControlMode
377         String powerControlMode = mService.getHdmiCecConfig().getStringValue(
378                 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE);
379         if (powerControlMode.equals(HdmiControlManager.POWER_CONTROL_MODE_NONE)) {
380             return;
381         }
382         oneTouchPlay(new IHdmiControlCallback.Stub() {
383             @Override
384             public void onComplete(int result) {
385                 if (result != HdmiControlManager.RESULT_SUCCESS) {
386                     Slog.w(TAG, "Failed to complete One Touch Play. result=" + result);
387                 }
388             }
389         });
390     }
391 
392     @Override
393     @CallSuper
394     @ServiceThreadOnly
395     @VisibleForTesting
setActiveSource(int logicalAddress, int physicalAddress, String caller)396     protected void setActiveSource(int logicalAddress, int physicalAddress, String caller) {
397         assertRunOnServiceThread();
398         super.setActiveSource(logicalAddress, physicalAddress, caller);
399         if (isActiveSource()) {
400             getWakeLock().acquire();
401         } else {
402             getWakeLock().release();
403         }
404     }
405 
406     @ServiceThreadOnly
getWakeLock()407     private ActiveWakeLock getWakeLock() {
408         assertRunOnServiceThread();
409         if (mWakeLock == null) {
410             if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) {
411                 mWakeLock = new SystemWakeLock();
412             } else {
413                 // Create a stub lock object that doesn't do anything about wake lock,
414                 // hence allows the device to go to sleep even if it's the active source.
415                 mWakeLock = new ActiveWakeLock() {
416                     @Override
417                     public void acquire() { }
418                     @Override
419                     public void release() { }
420                     @Override
421                     public boolean isHeld() { return false; }
422                 };
423                 HdmiLogger.debug("No wakelock is used to keep the display on.");
424             }
425         }
426         return mWakeLock;
427     }
428 
429     @Override
canGoToStandby()430     protected boolean canGoToStandby() {
431         return !getWakeLock().isHeld();
432     }
433 
434     @Override
435     @ServiceThreadOnly
onActiveSourceLost()436     protected void onActiveSourceLost() {
437         assertRunOnServiceThread();
438         mService.pauseActiveMediaSessions();
439         switch (mService.getHdmiCecConfig().getStringValue(
440                     HdmiControlManager.CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST)) {
441             case HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW:
442                 mDelayedPopupOnActiveSourceLostHandler.removeCallbacksAndMessages(null);
443                 mDelayedPopupOnActiveSourceLostHandler.postDelayed(
444                         new Runnable() {
445                             @Override
446                             public void run() {
447                                 if (isActiveSource()) {
448                                     return;
449                                 }
450 
451                                 if (getActiveSource().logicalAddress != Constants.ADDR_TV) {
452                                     startHdmiCecActiveSourceLostActivity();
453                                     mDelayedStandbyOnActiveSourceLostHandler
454                                             .removeCallbacksAndMessages(null);
455                                     mDelayedStandbyOnActiveSourceLostHandler.postDelayed(
456                                             new DelayedStandbyOnActiveSourceLostRunnable(),
457                                             STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
458                                     return;
459                                 }
460 
461                                 // We observed specific TV panels (old models) that send faulty CEC
462                                 // source changing messages, especially during wake-up.
463                                 // This request helps to check if the TV correctly asserted active
464                                 // source or not. If the request times out, active source is
465                                 // asserted by the local device.
466                                 addAndStartAction(new RequestActiveSourceAction(mService.playback(),
467                                         new IHdmiControlCallback.Stub() {
468                                     @Override
469                                     public void onComplete(int result) {
470                                         // If a device answers to <Request Active Source>, the
471                                         // pop-up should be triggered.
472                                         // During this action, the TV can switch to an HDMI input
473                                         // with a non-CEC capable device that won't be able to
474                                         // answer this request.
475                                         // In this case, the known active source would be
476                                         // represented by a valid physical address, but invalid
477                                         // logical address. The pop-up will be shown and the local
478                                         // device will not assert active source.
479                                         if (result == HdmiControlManager.RESULT_SUCCESS
480                                                 || getActiveSource().logicalAddress
481                                                 != Constants.ADDR_TV) {
482                                                 startHdmiCecActiveSourceLostActivity();
483                                                 mDelayedStandbyOnActiveSourceLostHandler
484                                                         .removeCallbacksAndMessages(null);
485                                                 mDelayedStandbyOnActiveSourceLostHandler
486                                                         .postDelayed(
487                                                                 new DelayedStandbyOnActiveSourceLostRunnable(),
488                                                         STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
489                                         } else {
490                                             // The request times out and the local device is not
491                                             // active source, but the TV previously asserted active
492                                             // source.
493                                             if (getActiveSource().logicalAddress
494                                                     == Constants.ADDR_TV) {
495                                                 mService.setAndBroadcastActiveSource(
496                                                         mService.getPhysicalAddress(),
497                                                         getDeviceInfo().getDeviceType(),
498                                                         Constants.ADDR_BROADCAST,
499                                                         "RequestActiveSourceAction#RESULT_TIMEOUT");
500                                             }
501                                         }
502                                     }}));
503                             }
504                         }, POPUP_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS);
505                 return;
506             case HdmiControlManager.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE:
507                 return;
508         }
509     }
510 
511     @VisibleForTesting
512     @ServiceThreadOnly
startHdmiCecActiveSourceLostActivity()513     void startHdmiCecActiveSourceLostActivity() {
514         final long identity = Binder.clearCallingIdentity();
515         try {
516             Context context = mService.getContext();
517             Intent intent = new Intent();
518             intent.setComponent(
519                     ComponentName.unflattenFromString(context.getResources().getString(
520                             com.android.internal.R.string.config_hdmiCecActiveSourceLostActivity
521                     )));
522             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
523             context.startActivityAsUser(intent, context.getUser());
524             mIsActiveSourceLostPopupLaunched = true;
525         } catch (ActivityNotFoundException e) {
526             Slog.e(TAG, "Unable to start HdmiCecActiveSourceLostActivity");
527         } finally {
528             Binder.restoreCallingIdentity(identity);
529         }
530     }
531 
532     @ServiceThreadOnly
533     @Constants.HandleMessageResult
handleUserControlPressed(HdmiCecMessage message)534     protected int handleUserControlPressed(HdmiCecMessage message) {
535         assertRunOnServiceThread();
536         wakeUpIfActiveSource();
537         return super.handleUserControlPressed(message);
538     }
539 
540     @ServiceThreadOnly
541     @Constants.HandleMessageResult
handleSetMenuLanguage(HdmiCecMessage message)542     protected int handleSetMenuLanguage(HdmiCecMessage message) {
543         assertRunOnServiceThread();
544         if (mService.getHdmiCecConfig().getIntValue(
545                 HdmiControlManager.CEC_SETTING_NAME_SET_MENU_LANGUAGE)
546                     == HdmiControlManager.SET_MENU_LANGUAGE_DISABLED) {
547             return Constants.ABORT_UNRECOGNIZED_OPCODE;
548         }
549 
550         try {
551             String iso3Language = new String(message.getParams(), 0, 3, "US-ASCII");
552             Locale currentLocale = mService.getContext().getResources().getConfiguration().locale;
553             String curIso3Language = mService.localeToMenuLanguage(currentLocale);
554             HdmiLogger.debug("handleSetMenuLanguage " + iso3Language + " cur:" + curIso3Language);
555             if (curIso3Language.equals(iso3Language)) {
556                 // Do not switch language if the new language is the same as the current one.
557                 // This helps avoid accidental country variant switching from en_US to en_AU
558                 // due to the limitation of CEC. See the warning below.
559                 return Constants.HANDLED;
560             }
561 
562             // Don't use Locale.getAvailableLocales() since it returns a locale
563             // which is not available on Settings.
564             final List<LocaleInfo> localeInfos = LocalePicker.getAllAssetLocales(
565                     mService.getContext(), false);
566             for (LocaleInfo localeInfo : localeInfos) {
567                 if (mService.localeToMenuLanguage(localeInfo.getLocale()).equals(iso3Language)) {
568                     // WARNING: CEC adopts ISO/FDIS-2 for language code, while Android requires
569                     // additional country variant to pinpoint the locale. This keeps the right
570                     // locale from being chosen. 'eng' in the CEC command, for instance,
571                     // will always be mapped to en-AU among other variants like en-US, en-GB,
572                     // an en-IN, which may not be the expected one.
573                     startSetMenuLanguageActivity(localeInfo.getLocale());
574                     return Constants.HANDLED;
575                 }
576             }
577             Slog.w(TAG, "Can't handle <Set Menu Language> of " + iso3Language);
578             return Constants.ABORT_INVALID_OPERAND;
579         } catch (UnsupportedEncodingException e) {
580             Slog.w(TAG, "Can't handle <Set Menu Language>", e);
581             return Constants.ABORT_INVALID_OPERAND;
582         }
583     }
584 
startSetMenuLanguageActivity(Locale locale)585     private void startSetMenuLanguageActivity(Locale locale) {
586         final long identity = Binder.clearCallingIdentity();
587         try {
588             Context context = mService.getContext();
589             Intent intent = new Intent();
590             intent.putExtra(HdmiControlManager.EXTRA_LOCALE, locale.toLanguageTag());
591             intent.setComponent(
592                     ComponentName.unflattenFromString(context.getResources().getString(
593                             com.android.internal.R.string.config_hdmiCecSetMenuLanguageActivity)));
594             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
595             context.startActivityAsUser(intent, context.getUser());
596         } catch (ActivityNotFoundException e) {
597             Slog.e(TAG, "unable to start HdmiCecSetMenuLanguageActivity");
598         } finally {
599             Binder.restoreCallingIdentity(identity);
600         }
601     }
602 
603     @Override
604     @Constants.HandleMessageResult
handleSetSystemAudioMode(HdmiCecMessage message)605     protected int handleSetSystemAudioMode(HdmiCecMessage message) {
606         // System Audio Mode only turns on/off when Audio System broadcasts on/off message.
607         // For device with type 4 and 5, it can set system audio mode on/off
608         // when there is another audio system device connected into the system first.
609         if (message.getDestination() != Constants.ADDR_BROADCAST
610                 || message.getSource() != Constants.ADDR_AUDIO_SYSTEM
611                 || mService.audioSystem() != null) {
612             return Constants.HANDLED;
613         }
614         boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message);
615         if (mService.isSystemAudioActivated() != setSystemAudioModeOn) {
616             mService.setSystemAudioActivated(setSystemAudioModeOn);
617         }
618         return Constants.HANDLED;
619     }
620 
621     @Override
622     @Constants.HandleMessageResult
handleSystemAudioModeStatus(HdmiCecMessage message)623     protected int handleSystemAudioModeStatus(HdmiCecMessage message) {
624         // Only directly addressed System Audio Mode Status message can change internal
625         // system audio mode status.
626         if (message.getDestination() == getDeviceInfo().getLogicalAddress()
627                 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM) {
628             boolean setSystemAudioModeOn = HdmiUtils.parseCommandParamSystemAudioStatus(message);
629             if (mService.isSystemAudioActivated() != setSystemAudioModeOn) {
630                 mService.setSystemAudioActivated(setSystemAudioModeOn);
631             }
632         }
633         return Constants.HANDLED;
634     }
635 
636     @Override
637     @ServiceThreadOnly
638     @Constants.HandleMessageResult
handleRoutingChange(HdmiCecMessage message)639     protected int handleRoutingChange(HdmiCecMessage message) {
640         assertRunOnServiceThread();
641         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams(), 2);
642         handleRoutingChangeAndInformation(physicalAddress, message);
643         return Constants.HANDLED;
644     }
645 
646     @Override
647     @ServiceThreadOnly
648     @Constants.HandleMessageResult
handleRoutingInformation(HdmiCecMessage message)649     protected int handleRoutingInformation(HdmiCecMessage message) {
650         assertRunOnServiceThread();
651         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
652         HdmiDeviceInfo sourceDevice = mService.getHdmiCecNetwork()
653                 .getCecDeviceInfo(message.getSource());
654         // Ignore <Routing Information> messages pointing to the same physical address as the
655         // message sender. In this case, we shouldn't consider the sender to be the active source.
656         // See more b/321771821#comment7.
657         if (sourceDevice != null
658                 && sourceDevice.getLogicalAddress() != Constants.ADDR_TV
659                 && sourceDevice.getPhysicalAddress() == physicalAddress) {
660             Slog.d(TAG, "<Routing Information> is ignored, it is pointing to the same physical"
661                     + " address as the message sender");
662             return Constants.HANDLED;
663         }
664         handleRoutingChangeAndInformation(physicalAddress, message);
665         return Constants.HANDLED;
666     }
667 
668     @Override
669     @ServiceThreadOnly
handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message)670     protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
671         assertRunOnServiceThread();
672         // If the device is active source and received a <Routing Change> or <Routing Information>
673         // message to a physical address in the same active path do not change the Active Source
674         // status.
675         // E.g. TV [0.0.0.0] ------ Switch [2.0.0.0] ------ OTT [2.1.0.0] (Active Source)
676         // TV sends <Routing Change>[2.0.0.0] -> OTT is still Active Source
677         // TV sends <Routing Change>[0.0.0.0] -> OTT is not Active Source anymore.
678         // TV sends <Routing Change>[3.0.0.0] -> OTT is not Active Source anymore.
679         if (HdmiUtils.isInActiveRoutingPath(mService.getPhysicalAddress(), physicalAddress)
680                 && physicalAddress != Constants.TV_PHYSICAL_ADDRESS
681                 && isActiveSource()) {
682             return;
683         }
684         if (physicalAddress != mService.getPhysicalAddress()) {
685             setActiveSource(physicalAddress,
686                     "HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()");
687             return;
688         }
689         if (!isActiveSource()) {
690             // If routing is changed to the device while Active Source, don't invalidate the
691             // Active Source
692             setActiveSource(physicalAddress,
693                     "HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()");
694         }
695         dismissUiOnActiveSourceStatusRecovered();
696         switch (mPlaybackDeviceActionOnRoutingControl) {
697             case WAKE_UP_AND_SEND_ACTIVE_SOURCE:
698                 setAndBroadcastActiveSource(message, physicalAddress,
699                         "HdmiCecLocalDevicePlayback#handleRoutingChangeAndInformation()");
700                 break;
701             case WAKE_UP_ONLY:
702                 mService.wakeUp();
703                 break;
704             case NONE:
705                 break;
706         }
707     }
708 
709     /**
710      * Called after logical address allocation is finished, allowing a local device to react to
711      * messages in the buffer before they are processed. This method may be used to cancel deferred
712      * actions.
713      */
714     @Override
preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages)715     protected void preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages) {
716         for (HdmiCecMessage message: bufferedMessages) {
717             // Prevent the device from broadcasting <Active Source> message if the active path
718             // changed during address allocation.
719             if (message.getOpcode() == Constants.MESSAGE_ROUTING_CHANGE
720                     || message.getOpcode() == Constants.MESSAGE_SET_STREAM_PATH
721                     || message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE) {
722                 removeAction(ActiveSourceAction.class);
723                 removeAction(OneTouchPlayAction.class);
724                 return;
725             }
726         }
727     }
728 
729     @Override
findKeyReceiverAddress()730     protected int findKeyReceiverAddress() {
731         return Constants.ADDR_TV;
732     }
733 
734     @Override
findAudioReceiverAddress()735     protected int findAudioReceiverAddress() {
736         if (mService.isSystemAudioActivated()) {
737             return Constants.ADDR_AUDIO_SYSTEM;
738         }
739         return Constants.ADDR_TV;
740     }
741 
isActiveSourceLostPopupLaunched()742     boolean isActiveSourceLostPopupLaunched() {
743         return mIsActiveSourceLostPopupLaunched;
744     }
745 
setIsActiveSourceLostPopupLaunched(boolean isActiveSourceLostPopupLaunched)746     void setIsActiveSourceLostPopupLaunched(boolean isActiveSourceLostPopupLaunched) {
747         mIsActiveSourceLostPopupLaunched = isActiveSourceLostPopupLaunched;
748     }
749 
750     @Override
751     @ServiceThreadOnly
disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback)752     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
753         assertRunOnServiceThread();
754         removeAction(DeviceDiscoveryAction.class);
755         removeAction(HotplugDetectionAction.class);
756         removeAction(NewDeviceAction.class);
757         removeAction(PowerStatusMonitorActionFromPlayback.class);
758         removeAction(RequestActiveSourceAction.class);
759         super.disableDevice(initiatedByCec, callback);
760         clearDeviceInfoList();
761         checkIfPendingActionsCleared();
762     }
763 
764     @Override
dump(final IndentingPrintWriter pw)765     protected void dump(final IndentingPrintWriter pw) {
766         super.dump(pw);
767         pw.println("isActiveSource(): " + isActiveSource());
768     }
769 
770     // Wrapper interface over PowerManager.WakeLock
771     private interface ActiveWakeLock {
acquire()772         void acquire();
release()773         void release();
isHeld()774         boolean isHeld();
775     }
776 
777     private class SystemWakeLock implements ActiveWakeLock {
778         private final WakeLockWrapper mWakeLock;
SystemWakeLock()779         public SystemWakeLock() {
780             mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
781             mWakeLock.setReferenceCounted(false);
782         }
783 
784         @Override
acquire()785         public void acquire() {
786             mWakeLock.acquire();
787             HdmiLogger.debug("active source: %b. Wake lock acquired", isActiveSource());
788         }
789 
790         @Override
release()791         public void release() {
792             mWakeLock.release();
793             HdmiLogger.debug("Wake lock released");
794         }
795 
796         @Override
isHeld()797         public boolean isHeld() {
798             return mWakeLock.isHeld();
799         }
800     }
801 }
802