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.HdmiTvClient;
     22 import android.hardware.hdmi.IHdmiControlCallback;
     23 import android.hardware.tv.cec.V1_0.SendMessageResult;
     24 import android.os.RemoteException;
     25 import android.util.Slog;
     26 import com.android.server.hdmi.HdmiControlService.SendMessageCallback;
     27 
     28 /**
     29  * Handles an action that selects a logical device as a new active source.
     30  *
     31  * Triggered by {@link HdmiTvClient}, attempts to select the given target device
     32  * for a new active source. It does its best to wake up the target in standby mode
     33  * before issuing the command >Set Stream path<.
     34  */
     35 final class DeviceSelectAction extends HdmiCecFeatureAction {
     36     private static final String TAG = "DeviceSelect";
     37 
     38     // Time in milliseconds we wait for the device power status to switch to 'Standby'
     39     private static final int TIMEOUT_TRANSIT_TO_STANDBY_MS = 5 * 1000;
     40 
     41     // Time in milliseconds we wait for the device power status to turn to 'On'.
     42     private static final int TIMEOUT_POWER_ON_MS = 5 * 1000;
     43 
     44     // The number of times we try to wake up the target device before we give up
     45     // and just send <Set Stream Path>.
     46     private static final int LOOP_COUNTER_MAX = 20;
     47 
     48     // State in which we wait for <Report Power Status> to come in response to the command
     49     // <Give Device Power Status> we have sent.
     50     private static final int STATE_WAIT_FOR_REPORT_POWER_STATUS = 1;
     51 
     52     // State in which we wait for the device power status to switch to 'Standby'.
     53     // We wait till the status becomes 'Standby' before we send <Set Stream Path>
     54     // to wake up the device again.
     55     private static final int STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY = 2;
     56 
     57     // State in which we wait for the device power status to switch to 'on'. We wait
     58     // maximum 100 seconds (20 * 5) before we give up and just send <Set Stream Path>.
     59     private static final int STATE_WAIT_FOR_DEVICE_POWER_ON = 3;
     60 
     61     private final HdmiDeviceInfo mTarget;
     62     private final IHdmiControlCallback mCallback;
     63     private final HdmiCecMessage mGivePowerStatus;
     64 
     65     private int mPowerStatusCounter = 0;
     66 
     67     /**
     68      * Constructor.
     69      *
     70      * @param source {@link HdmiCecLocalDevice} instance
     71      * @param target target logical device that will be a new active source
     72      * @param callback callback object
     73      */
     74     public DeviceSelectAction(HdmiCecLocalDeviceTv source,
     75             HdmiDeviceInfo target, IHdmiControlCallback callback) {
     76         super(source);
     77         mCallback = callback;
     78         mTarget = target;
     79         mGivePowerStatus = HdmiCecMessageBuilder.buildGiveDevicePowerStatus(
     80                 getSourceAddress(), getTargetAddress());
     81     }
     82 
     83     int getTargetAddress() {
     84         return mTarget.getLogicalAddress();
     85     }
     86 
     87     @Override
     88     public boolean start() {
     89         // Seq #9
     90         queryDevicePowerStatus();
     91         return true;
     92     }
     93 
     94     private void queryDevicePowerStatus() {
     95         sendCommand(mGivePowerStatus, new SendMessageCallback() {
     96             @Override
     97             public void onSendCompleted(int error) {
     98                 if (error != SendMessageResult.SUCCESS) {
     99                     invokeCallback(HdmiControlManager.RESULT_COMMUNICATION_FAILED);
    100                     finish();
    101                     return;
    102                 }
    103             }
    104         });
    105         mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
    106         addTimer(mState, HdmiConfig.TIMEOUT_MS);
    107     }
    108 
    109     @Override
    110     public boolean processCommand(HdmiCecMessage cmd) {
    111         if (cmd.getSource() != getTargetAddress()) {
    112             return false;
    113         }
    114         int opcode = cmd.getOpcode();
    115         byte[] params = cmd.getParams();
    116 
    117         switch (mState) {
    118             case STATE_WAIT_FOR_REPORT_POWER_STATUS:
    119                 if (opcode == Constants.MESSAGE_REPORT_POWER_STATUS) {
    120                     return handleReportPowerStatus(params[0]);
    121                 }
    122                 return false;
    123             default:
    124                 break;
    125         }
    126         return false;
    127     }
    128 
    129     private boolean handleReportPowerStatus(int powerStatus) {
    130         switch (powerStatus) {
    131             case HdmiControlManager.POWER_STATUS_ON:
    132                 sendSetStreamPath();
    133                 return true;
    134             case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY:
    135                 if (mPowerStatusCounter < 4) {
    136                     mState = STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY;
    137                     addTimer(mState, TIMEOUT_TRANSIT_TO_STANDBY_MS);
    138                 } else {
    139                     sendSetStreamPath();
    140                 }
    141                 return true;
    142             case HdmiControlManager.POWER_STATUS_STANDBY:
    143                 if (mPowerStatusCounter == 0) {
    144                     turnOnDevice();
    145                 } else {
    146                     sendSetStreamPath();
    147                 }
    148                 return true;
    149             case HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON:
    150                 if (mPowerStatusCounter < LOOP_COUNTER_MAX) {
    151                     mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
    152                     addTimer(mState, TIMEOUT_POWER_ON_MS);
    153                 } else {
    154                     sendSetStreamPath();
    155                 }
    156                 return true;
    157         }
    158         return false;
    159     }
    160 
    161     private void turnOnDevice() {
    162         sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
    163                 HdmiCecKeycode.CEC_KEYCODE_POWER);
    164         sendUserControlPressedAndReleased(mTarget.getLogicalAddress(),
    165                 HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION);
    166         mState = STATE_WAIT_FOR_DEVICE_POWER_ON;
    167         addTimer(mState, TIMEOUT_POWER_ON_MS);
    168     }
    169 
    170     private void sendSetStreamPath() {
    171         // Turn the active source invalidated, which remains so till <Active Source> comes from
    172         // the selected device.
    173         tv().getActiveSource().invalidate();
    174         tv().setActivePath(mTarget.getPhysicalAddress());
    175         sendCommand(HdmiCecMessageBuilder.buildSetStreamPath(
    176                 getSourceAddress(), mTarget.getPhysicalAddress()));
    177         invokeCallback(HdmiControlManager.RESULT_SUCCESS);
    178         finish();
    179     }
    180 
    181     @Override
    182     public void handleTimerEvent(int timeoutState) {
    183         if (mState != timeoutState) {
    184             Slog.w(TAG, "Timer in a wrong state. Ignored.");
    185             return;
    186         }
    187         switch (mState) {
    188             case STATE_WAIT_FOR_REPORT_POWER_STATUS:
    189                 if (tv().isPowerStandbyOrTransient()) {
    190                     invokeCallback(HdmiControlManager.RESULT_INCORRECT_MODE);
    191                     finish();
    192                     return;
    193                 }
    194                 sendSetStreamPath();
    195                 break;
    196             case STATE_WAIT_FOR_DEVICE_TO_TRANSIT_TO_STANDBY:
    197             case STATE_WAIT_FOR_DEVICE_POWER_ON:
    198                 mPowerStatusCounter++;
    199                 queryDevicePowerStatus();
    200                 break;
    201         }
    202     }
    203 
    204     private void invokeCallback(int result) {
    205         if (mCallback == null) {
    206             return;
    207         }
    208         try {
    209             mCallback.onComplete(result);
    210         } catch (RemoteException e) {
    211             Slog.e(TAG, "Callback failed:" + e);
    212         }
    213     }
    214 }
    215