content: /*
   * Copyright (C) 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.android.server.hdmi;
  
  import android.hardware.hdmi.HdmiControlManager;
  import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
  import android.hardware.hdmi.IHdmiControlCallback;
  import android.os.RemoteException;
  import android.util.Slog;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * Feature action that performs one touch play against TV/Display device. This action is initiated
   * via {@link android.hardware.hdmi.HdmiPlaybackClient#oneTouchPlay(OneTouchPlayCallback)} from the
   * Android system working as playback device to turn on the TV, and switch the input.
   * <p>
   * Package-private, accessed by {@link HdmiControlService} only.
   */
  final class OneTouchPlayAction extends HdmiCecFeatureAction {
      private static final String TAG = "OneTouchPlayAction";
  
      // State in which the action is waiting for <Report Power Status>. In normal situation
      // source device can simply send <Text|Image View On> and <Active Source> in succession
      // since the standard requires that the TV/Display should buffer the <Active Source>
      // if the TV is brought of out standby state.
      //
      // But there are TV's that fail to buffer the <Active Source> while getting out of
      // standby mode, and do not accept the command until their power status becomes 'ON'.
      // For a workaround, we send <Give Device Power Status> commands periodically to make sure
      // the device switches its status to 'ON'. Then we send additional <Active Source>.
      private static final int STATE_WAITING_FOR_REPORT_POWER_STATUS = 1;
  
      // The maximum number of times we send <Give Device Power Status> before we give up.
      // We wait up to RESPONSE_TIMEOUT_MS * LOOP_COUNTER_MAX = 20 seconds.
      private static final int LOOP_COUNTER_MAX = 10;
  
      private final int mTargetAddress;
      private final List<IHdmiControlCallback> mCallbacks = new ArrayList<>();
  
      private int mPowerStatusCounter = 0;
  
      // Factory method. Ensures arguments are valid.
      static OneTouchPlayAction create(HdmiCecLocalDeviceSource source,
              int targetAddress, IHdmiControlCallback callback) {
          if (source == null || callback == null) {
              Slog.e(TAG, "Wrong arguments");
              return null;
          }
          return new OneTouchPlayAction(source, targetAddress,
                  callback);
      }
  
      private OneTouchPlayAction(HdmiCecLocalDevice localDevice, int targetAddress,
              IHdmiControlCallback callback) {
          super(localDevice);
          mTargetAddress = targetAddress;
          addCallback(callback);
      }
  
      @Override
      boolean start() {
          sendCommand(HdmiCecMessageBuilder.buildTextViewOn(getSourceAddress(), mTargetAddress));
          broadcastActiveSource();
          queryDevicePowerStatus();
          mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
          addTimer(mState, HdmiConfig.TIMEOUT_MS);
          return true;
      }
  
      private void broadcastActiveSource() {
          // Because only source device can create this action, it's safe to cast.
          HdmiCecLocalDeviceSource source = source();
          source.mService.setAndBroadcastActiveSourceFromOneDeviceType(
                  mTargetAddress, getSourcePath());
          // Set local active port to HOME when One Touch Play.
          // Active Port and Current Input are handled by the switch functionality device.
          if (source.mService.audioSystem() != null) {
              source = source.mService.audioSystem();
          }
          if (source.getLocalActivePort() != Constants.CEC_SWITCH_HOME) {
              source.switchInputOnReceivingNewActivePath(getSourcePath());
          }
          source.setRoutingPort(Constants.CEC_SWITCH_HOME);
          source.setLocalActivePort(Constants.CEC_SWITCH_HOME);
      }
  
      private void queryDevicePowerStatus() {
          sendCommand(HdmiCecMessageBuilder.buildGiveDevicePowerStatus(getSourceAddress(),
                  mTargetAddress));
      }
  
      @Override
      boolean processCommand(HdmiCecMessage cmd) {
          if (mState != STATE_WAITING_FOR_REPORT_POWER_STATUS
                  || mTargetAddress != cmd.getSource()) {
              return false;
          }
          if (cmd.getOpcode() == Constants.MESSAGE_REPORT_POWER_STATUS) {
              int status = cmd.getParams()[0];
              if (status == HdmiControlManager.POWER_STATUS_ON) {
                  broadcastActiveSource();
                  invokeCallback(HdmiControlManager.RESULT_SUCCESS);
                  finish();
              }
              return true;
          }
          return false;
      }
  
      @Override
      void handleTimerEvent(int state) {
          if (mState != state) {
              return;
          }
          if (state == STATE_WAITING_FOR_REPORT_POWER_STATUS) {
              if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) {
                  queryDevicePowerStatus();
                  addTimer(mState, HdmiConfig.TIMEOUT_MS);
              } else {
                  // Couldn't wake up the TV for whatever reason. Report failure.
                  invokeCallback(HdmiControlManager.RESULT_TIMEOUT);
                  finish();
              }
          }
      }
  
      public void addCallback(IHdmiControlCallback callback) {
          mCallbacks.add(callback);
      }
  
      private void invokeCallback(int result) {
          try {
              for (IHdmiControlCallback callback : mCallbacks) {
                  callback.onComplete(result);
              }
          } catch (RemoteException e) {
              Slog.e(TAG, "Callback failed:" + e);
          }
      }
  }
  