Home | History | Annotate | Download | only in hdmi
      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.hardware.hdmi.HdmiControlManager;
     20 import android.hardware.hdmi.HdmiDeviceInfo;
     21 import android.hardware.hdmi.IHdmiControlCallback;
     22 import android.os.PowerManager;
     23 import android.os.PowerManager.WakeLock;
     24 import android.os.RemoteException;
     25 import android.os.SystemProperties;
     26 import android.util.Slog;
     27 
     28 import com.android.internal.util.IndentingPrintWriter;
     29 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
     30 
     31 /**
     32  * Represent a logical device of type Playback residing in Android system.
     33  */
     34 final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
     35     private static final String TAG = "HdmiCecLocalDevicePlayback";
     36 
     37     private boolean mIsActiveSource = false;
     38 
     39     // Used to keep the device awake while it is the active source. For devices that
     40     // cannot wake up via CEC commands, this address the inconvenience of having to
     41     // turn them on.
     42     // Lazily initialized - should call getWakeLock() to get the instance.
     43     private WakeLock mWakeLock;
     44 
     45     HdmiCecLocalDevicePlayback(HdmiControlService service) {
     46         super(service, HdmiDeviceInfo.DEVICE_PLAYBACK);
     47     }
     48 
     49     @Override
     50     @ServiceThreadOnly
     51     protected void onAddressAllocated(int logicalAddress, int reason) {
     52         assertRunOnServiceThread();
     53         mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
     54                 mAddress, mService.getPhysicalAddress(), mDeviceType));
     55         mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
     56                 mAddress, mService.getVendorId()));
     57         startQueuedActions();
     58     }
     59 
     60     @Override
     61     @ServiceThreadOnly
     62     protected int getPreferredAddress() {
     63         assertRunOnServiceThread();
     64         return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
     65                 Constants.ADDR_UNREGISTERED);
     66     }
     67 
     68     @Override
     69     @ServiceThreadOnly
     70     protected void setPreferredAddress(int addr) {
     71         assertRunOnServiceThread();
     72         SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
     73                 String.valueOf(addr));
     74     }
     75 
     76     @ServiceThreadOnly
     77     void oneTouchPlay(IHdmiControlCallback callback) {
     78         assertRunOnServiceThread();
     79         if (hasAction(OneTouchPlayAction.class)) {
     80             Slog.w(TAG, "oneTouchPlay already in progress");
     81             invokeCallback(callback, HdmiControlManager.RESULT_ALREADY_IN_PROGRESS);
     82             return;
     83         }
     84 
     85         // TODO: Consider the case of multiple TV sets. For now we always direct the command
     86         //       to the primary one.
     87         OneTouchPlayAction action = OneTouchPlayAction.create(this, Constants.ADDR_TV,
     88                 callback);
     89         if (action == null) {
     90             Slog.w(TAG, "Cannot initiate oneTouchPlay");
     91             invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
     92             return;
     93         }
     94         addAndStartAction(action);
     95     }
     96 
     97     @ServiceThreadOnly
     98     void queryDisplayStatus(IHdmiControlCallback callback) {
     99         assertRunOnServiceThread();
    100         if (hasAction(DevicePowerStatusAction.class)) {
    101             Slog.w(TAG, "queryDisplayStatus already in progress");
    102             invokeCallback(callback, HdmiControlManager.RESULT_ALREADY_IN_PROGRESS);
    103             return;
    104         }
    105         DevicePowerStatusAction action = DevicePowerStatusAction.create(this,
    106                 Constants.ADDR_TV, callback);
    107         if (action == null) {
    108             Slog.w(TAG, "Cannot initiate queryDisplayStatus");
    109             invokeCallback(callback, HdmiControlManager.RESULT_EXCEPTION);
    110             return;
    111         }
    112         addAndStartAction(action);
    113     }
    114 
    115     @ServiceThreadOnly
    116     private void invokeCallback(IHdmiControlCallback callback, int result) {
    117         assertRunOnServiceThread();
    118         try {
    119             callback.onComplete(result);
    120         } catch (RemoteException e) {
    121             Slog.e(TAG, "Invoking callback failed:" + e);
    122         }
    123     }
    124 
    125     @Override
    126     @ServiceThreadOnly
    127     void onHotplug(int portId, boolean connected) {
    128         assertRunOnServiceThread();
    129         mCecMessageCache.flushAll();
    130         // We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
    131         if (connected && mService.isPowerStandbyOrTransient()) {
    132             mService.wakeUp();
    133         }
    134         if (!connected) {
    135             getWakeLock().release();
    136         }
    137     }
    138 
    139     @ServiceThreadOnly
    140     void setActiveSource(boolean on) {
    141         assertRunOnServiceThread();
    142         mIsActiveSource = on;
    143         if (on) {
    144             getWakeLock().acquire();
    145             HdmiLogger.debug("active source: %b. Wake lock acquired", mIsActiveSource);
    146         } else {
    147             getWakeLock().release();
    148             HdmiLogger.debug("Wake lock released");
    149         }
    150     }
    151 
    152     @ServiceThreadOnly
    153     private WakeLock getWakeLock() {
    154         assertRunOnServiceThread();
    155         if (mWakeLock == null) {
    156             mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
    157             mWakeLock.setReferenceCounted(false);
    158         }
    159         return mWakeLock;
    160     }
    161 
    162     @Override
    163     protected boolean canGoToStandby() {
    164         return !getWakeLock().isHeld();
    165     }
    166 
    167     @Override
    168     @ServiceThreadOnly
    169     protected boolean handleActiveSource(HdmiCecMessage message) {
    170         assertRunOnServiceThread();
    171         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
    172         mayResetActiveSource(physicalAddress);
    173         return true;  // Broadcast message.
    174     }
    175 
    176     private void mayResetActiveSource(int physicalAddress) {
    177         if (physicalAddress != mService.getPhysicalAddress()) {
    178             setActiveSource(false);
    179         }
    180     }
    181 
    182     @ServiceThreadOnly
    183     protected boolean handleUserControlPressed(HdmiCecMessage message) {
    184         assertRunOnServiceThread();
    185         wakeUpIfActiveSource();
    186         return super.handleUserControlPressed(message);
    187     }
    188 
    189     @Override
    190     @ServiceThreadOnly
    191     protected boolean handleSetStreamPath(HdmiCecMessage message) {
    192         assertRunOnServiceThread();
    193         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
    194         maySetActiveSource(physicalAddress);
    195         maySendActiveSource(message.getSource());
    196         wakeUpIfActiveSource();
    197         return true;  // Broadcast message.
    198     }
    199 
    200     // Samsung model we tested sends <Routing Change> and <Request Active Source>
    201     // in a row, and then changes the input to the internal source if there is no
    202     // <Active Source> in response. To handle this, we'll set ActiveSource aggressively.
    203     @Override
    204     @ServiceThreadOnly
    205     protected boolean handleRoutingChange(HdmiCecMessage message) {
    206         assertRunOnServiceThread();
    207         int newPath = HdmiUtils.twoBytesToInt(message.getParams(), 2);
    208         maySetActiveSource(newPath);
    209         return true;  // Broadcast message.
    210     }
    211 
    212     @Override
    213     @ServiceThreadOnly
    214     protected boolean handleRoutingInformation(HdmiCecMessage message) {
    215         assertRunOnServiceThread();
    216         int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams());
    217         maySetActiveSource(physicalAddress);
    218         return true;  // Broadcast message.
    219     }
    220 
    221     private void maySetActiveSource(int physicalAddress) {
    222         setActiveSource(physicalAddress == mService.getPhysicalAddress());
    223     }
    224 
    225     private void wakeUpIfActiveSource() {
    226         if (!mIsActiveSource) {
    227             return;
    228         }
    229         // Wake up the device if the power is in standby mode, or its screen is off -
    230         // which can happen if the device is holding a partial lock.
    231         if (mService.isPowerStandbyOrTransient() || !mService.getPowerManager().isScreenOn()) {
    232             mService.wakeUp();
    233         }
    234     }
    235 
    236     private void maySendActiveSource(int dest) {
    237         if (mIsActiveSource) {
    238             mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
    239                     mAddress, mService.getPhysicalAddress()));
    240             // Always reports menu-status active to receive RCP.
    241             mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus(
    242                     mAddress, dest, Constants.MENU_STATE_ACTIVATED));
    243         }
    244     }
    245 
    246     @Override
    247     @ServiceThreadOnly
    248     protected boolean handleRequestActiveSource(HdmiCecMessage message) {
    249         assertRunOnServiceThread();
    250         maySendActiveSource(message.getSource());
    251         return true;  // Broadcast message.
    252     }
    253 
    254     @Override
    255     @ServiceThreadOnly
    256     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
    257         super.disableDevice(initiatedByCec, callback);
    258 
    259         assertRunOnServiceThread();
    260         if (!initiatedByCec && mIsActiveSource) {
    261             mService.sendCecCommand(HdmiCecMessageBuilder.buildInactiveSource(
    262                     mAddress, mService.getPhysicalAddress()));
    263         }
    264         setActiveSource(false);
    265         checkIfPendingActionsCleared();
    266     }
    267 
    268     @Override
    269     protected void dump(final IndentingPrintWriter pw) {
    270         super.dump(pw);
    271         pw.println("mIsActiveSource: " + mIsActiveSource);
    272     }
    273 }
    274