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.os.Handler;
  import android.os.Looper;
  import android.os.Message;
  import android.util.Pair;
  import android.util.Slog;
  
  import com.android.internal.annotations.VisibleForTesting;
  import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * Encapsulates a sequence of CEC command exchange for a certain feature.
   * <p>
   * Many CEC features are accomplished by CEC devices on the bus exchanging more than one
   * command. {@link HdmiCecFeatureAction} represents the life cycle of the communication, manages the
   * state as the process progresses, and if necessary, returns the result to the caller which
   * initiates the action, through the callback given at the creation of the object. All the actual
   * action classes inherit FeatureAction.
   * <p>
   * More than one FeatureAction objects can be up and running simultaneously, maintained by
   * {@link HdmiCecLocalDevice}. Each action is passed a new command arriving from the bus, and either
   * consumes it if the command is what the action expects, or yields it to other action. Declared as
   * package private, accessed by {@link HdmiControlService} only.
   */
  abstract class HdmiCecFeatureAction {
      private static final String TAG = "HdmiCecFeatureAction";
  
      // Timer handler message used for timeout event
      protected static final int MSG_TIMEOUT = 100;
  
      // Default state used in common by all the feature actions.
      protected static final int STATE_NONE = 0;
  
      // Internal state indicating the progress of action.
      protected int mState = STATE_NONE;
  
      private final HdmiControlService mService;
      private final HdmiCecLocalDevice mSource;
  
      // Timer that manages timeout events.
      protected ActionTimer mActionTimer;
  
      private ArrayList<Pair<HdmiCecFeatureAction, Runnable>> mOnFinishedCallbacks;
  
      HdmiCecFeatureAction(HdmiCecLocalDevice source) {
          mSource = source;
          mService = mSource.getService();
          mActionTimer = createActionTimer(mService.getServiceLooper());
      }
  
      @VisibleForTesting
      void setActionTimer(ActionTimer actionTimer) {
          mActionTimer = actionTimer;
      }
  
      /**
       * Called after the action is created. Initialization or first step to take
       * for the action can be done in this method. Shall update {@code mState} to
       * indicate that the action has started.
       *
       * @return true if the operation is successful; otherwise false.
       */
      abstract boolean start();
  
      /**
       * Process the command. Called whenever a new command arrives.
       *
       * @param cmd command to process
       * @return true if the command was consumed in the process; Otherwise false.
       */
      abstract boolean processCommand(HdmiCecMessage cmd);
  
      /**
       * Called when the action should handle the timer event it created before.
       *
       * <p>CEC standard mandates each command transmission should be responded within
       * certain period of time. The method is called when the timer it created as it transmitted
       * a command gets expired. Inner logic should take an appropriate action.
       *
       * @param state the state associated with the time when the timer was created
       */
      abstract void handleTimerEvent(int state);
  
      /**
       * Timer handler interface used for FeatureAction classes.
       */
      interface ActionTimer {
          /**
           * Send a timer message.
           *
           * Also carries the state of the action when the timer is created. Later this state is
           * compared to the one the action is in when it receives the timer to let the action tell
           * the right timer to handle.
           *
           * @param state state of the action is in
           * @param delayMillis amount of delay for the timer
           */
          void sendTimerMessage(int state, long delayMillis);
  
          /**
           * Removes any pending timer message.
           */
          void clearTimerMessage();
      }
  
      private class ActionTimerHandler extends Handler implements ActionTimer {
  
          public ActionTimerHandler(Looper looper) {
              super(looper);
          }
  
          @Override
          public void sendTimerMessage(int state, long delayMillis) {
              // The third argument(0) is not used.
              sendMessageDelayed(obtainMessage(MSG_TIMEOUT, state, 0), delayMillis);
          }
  
          @Override
          public void clearTimerMessage() {
              removeMessages(MSG_TIMEOUT);
          }
  
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what) {
              case MSG_TIMEOUT:
                  handleTimerEvent(msg.arg1);
                  break;
              default:
                  Slog.w(TAG, "Unsupported message:" + msg.what);
                  break;
              }
          }
      }
  
      private ActionTimer createActionTimer(Looper looper) {
          return new ActionTimerHandler(looper);
      }
  
      // Add a new timer. The timer event will come to mActionTimer.handleMessage() in
      // delayMillis.
      protected void addTimer(int state, int delayMillis) {
          mActionTimer.sendTimerMessage(state, delayMillis);
      }
  
      boolean started() {
          return mState != STATE_NONE;
      }
  
      protected final void sendCommand(HdmiCecMessage cmd) {
          mService.sendCecCommand(cmd);
      }
  
      protected final void sendCommand(HdmiCecMessage cmd,
              HdmiControlService.SendMessageCallback callback) {
          mService.sendCecCommand(cmd, callback);
      }
  
      protected final void addAndStartAction(HdmiCecFeatureAction action) {
          mSource.addAndStartAction(action);
      }
  
      protected final <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) {
          return mSource.getActions(clazz);
      }
  
      protected final HdmiCecMessageCache getCecMessageCache() {
          return mSource.getCecMessageCache();
      }
  
      /**
       * Remove the action from the action queue. This is called after the action finishes
       * its role.
       *
       * @param action
       */
      protected final void removeAction(HdmiCecFeatureAction action) {
          mSource.removeAction(action);
      }
  
      protected final <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) {
          mSource.removeActionExcept(clazz, null);
      }
  
      protected final <T extends HdmiCecFeatureAction> void removeActionExcept(final Class<T> clazz,
              final HdmiCecFeatureAction exception) {
          mSource.removeActionExcept(clazz, exception);
      }
  
      protected final void pollDevices(DevicePollingCallback callback, int pickStrategy,
              int retryCount) {
          mService.pollDevices(callback, getSourceAddress(), pickStrategy, retryCount);
      }
  
      /**
       * Clean up action's state.
       *
       * <p>Declared as package-private. Only {@link HdmiControlService} can access it.
       */
      void clear() {
          mState = STATE_NONE;
          // Clear all timers.
          mActionTimer.clearTimerMessage();
      }
  
      /**
       * Finish up the action. Reset the state, and remove itself from the action queue.
       */
      protected void finish() {
          finish(true);
      }
  
      void finish(boolean removeSelf) {
          clear();
          if (removeSelf) {
              removeAction(this);
          }
          if (mOnFinishedCallbacks != null) {
              for (Pair<HdmiCecFeatureAction, Runnable> actionCallbackPair: mOnFinishedCallbacks) {
                  if (actionCallbackPair.first.mState != STATE_NONE) {
                      actionCallbackPair.second.run();
                  }
              }
              mOnFinishedCallbacks = null;
          }
      }
  
      protected final HdmiCecLocalDevice localDevice() {
          return mSource;
      }
  
      protected final HdmiCecLocalDevicePlayback playback() {
          return (HdmiCecLocalDevicePlayback) mSource;
      }
  
      protected final HdmiCecLocalDeviceSource source() {
          return (HdmiCecLocalDeviceSource) mSource;
      }
  
      protected final HdmiCecLocalDeviceTv tv() {
          return (HdmiCecLocalDeviceTv) mSource;
      }
  
      protected final HdmiCecLocalDeviceAudioSystem audioSystem() {
          return (HdmiCecLocalDeviceAudioSystem) mSource;
      }
  
      protected final int getSourceAddress() {
          return mSource.getDeviceInfo().getLogicalAddress();
      }
  
      protected final int getSourcePath() {
          return mSource.getDeviceInfo().getPhysicalAddress();
      }
  
      protected final void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
          mSource.sendUserControlPressedAndReleased(targetAddress, uiCommand);
      }
  
      protected final void addOnFinishedCallback(HdmiCecFeatureAction action, Runnable runnable) {
          if (mOnFinishedCallbacks == null) {
              mOnFinishedCallbacks = new ArrayList<>();
          }
          mOnFinishedCallbacks.add(Pair.create(action, runnable));
      }
  }
  