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.HdmiDeviceInfo;
     20 import android.util.Slog;
     21 
     22 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
     23 
     24 import java.util.BitSet;
     25 import java.util.List;
     26 
     27 /**
     28  * Feature action that handles hot-plug detection mechanism.
     29  * Hot-plug event is initiated by timer after device discovery action.
     30  *
     31  * <p>Check all devices every 15 secs except for system audio.
     32  * If system audio is on, check hot-plug for audio system every 5 secs.
     33  * For other devices, keep 15 secs period.
     34  */
     35 // Seq #3
     36 final class HotplugDetectionAction extends HdmiCecFeatureAction {
     37     private static final String TAG = "HotPlugDetectionAction";
     38 
     39     private static final int POLLING_INTERVAL_MS = 5000;
     40     private static final int TIMEOUT_COUNT = 3;
     41     private static final int AVR_COUNT_MAX = 3;
     42 
     43     // State in which waits for next polling
     44     private static final int STATE_WAIT_FOR_NEXT_POLLING = 1;
     45 
     46     // All addresses except for broadcast (unregistered address).
     47     private static final int NUM_OF_ADDRESS = Constants.ADDR_SPECIFIC_USE
     48             - Constants.ADDR_TV + 1;
     49 
     50     private int mTimeoutCount = 0;
     51 
     52     // Counter used to ensure the connection to AVR is stable. Occasional failure to get
     53     // polling response from AVR despite its presence leads to unstable status flipping.
     54     // This is a workaround to deal with it, by removing the device only if the removal
     55     // is detected {@code AVR_COUNT_MAX} times in a row.
     56     private int mAvrStatusCount = 0;
     57 
     58     /**
     59      * Constructor
     60      *
     61      * @param source {@link HdmiCecLocalDevice} instance
     62      */
     63     HotplugDetectionAction(HdmiCecLocalDevice source) {
     64         super(source);
     65     }
     66 
     67     @Override
     68     boolean start() {
     69         Slog.v(TAG, "Hot-plug dection started.");
     70 
     71         mState = STATE_WAIT_FOR_NEXT_POLLING;
     72         mTimeoutCount = 0;
     73 
     74         // Start timer without polling.
     75         // The first check for all devices will be initiated 15 seconds later.
     76         addTimer(mState, POLLING_INTERVAL_MS);
     77         return true;
     78     }
     79 
     80     @Override
     81     boolean processCommand(HdmiCecMessage cmd) {
     82         // No-op
     83         return false;
     84     }
     85 
     86     @Override
     87     void handleTimerEvent(int state) {
     88         if (mState != state) {
     89             return;
     90         }
     91 
     92         if (mState == STATE_WAIT_FOR_NEXT_POLLING) {
     93             mTimeoutCount = (mTimeoutCount + 1) % TIMEOUT_COUNT;
     94             pollDevices();
     95         }
     96     }
     97 
     98     /**
     99      * Start device polling immediately.
    100      */
    101     void pollAllDevicesNow() {
    102         // Clear existing timer to avoid overlapped execution
    103         mActionTimer.clearTimerMessage();
    104 
    105         mTimeoutCount = 0;
    106         mState = STATE_WAIT_FOR_NEXT_POLLING;
    107         pollAllDevices();
    108 
    109         addTimer(mState, POLLING_INTERVAL_MS);
    110     }
    111 
    112     // This method is called every 5 seconds.
    113     private void pollDevices() {
    114         // All device check called every 15 seconds.
    115         if (mTimeoutCount == 0) {
    116             pollAllDevices();
    117         } else {
    118             if (tv().isSystemAudioActivated()) {
    119                 pollAudioSystem();
    120             }
    121         }
    122 
    123         addTimer(mState, POLLING_INTERVAL_MS);
    124     }
    125 
    126     private void pollAllDevices() {
    127         Slog.v(TAG, "Poll all devices.");
    128 
    129         pollDevices(new DevicePollingCallback() {
    130             @Override
    131             public void onPollingFinished(List<Integer> ackedAddress) {
    132                 checkHotplug(ackedAddress, false);
    133             }
    134         }, Constants.POLL_ITERATION_IN_ORDER
    135                 | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.HOTPLUG_DETECTION_RETRY);
    136     }
    137 
    138     private void pollAudioSystem() {
    139         Slog.v(TAG, "Poll audio system.");
    140 
    141         pollDevices(new DevicePollingCallback() {
    142             @Override
    143             public void onPollingFinished(List<Integer> ackedAddress) {
    144                 checkHotplug(ackedAddress, true);
    145             }
    146         }, Constants.POLL_ITERATION_IN_ORDER
    147                 | Constants.POLL_STRATEGY_SYSTEM_AUDIO, HdmiConfig.HOTPLUG_DETECTION_RETRY);
    148     }
    149 
    150     private void checkHotplug(List<Integer> ackedAddress, boolean audioOnly) {
    151         BitSet currentInfos = infoListToBitSet(tv().getDeviceInfoList(false), audioOnly);
    152         BitSet polledResult = addressListToBitSet(ackedAddress);
    153 
    154         // At first, check removed devices.
    155         BitSet removed = complement(currentInfos, polledResult);
    156         int index = -1;
    157         while ((index = removed.nextSetBit(index + 1)) != -1) {
    158             if (index == Constants.ADDR_AUDIO_SYSTEM) {
    159                 HdmiDeviceInfo avr = tv().getAvrDeviceInfo();
    160                 if (avr != null && tv().isConnected(avr.getPortId())) {
    161                     ++mAvrStatusCount;
    162                     Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount);
    163                     if (mAvrStatusCount < AVR_COUNT_MAX) {
    164                         continue;
    165                     }
    166                 }
    167             }
    168             Slog.v(TAG, "Remove device by hot-plug detection:" + index);
    169             removeDevice(index);
    170         }
    171 
    172         // Reset the counter if the ack is returned from AVR.
    173         if (!removed.get(Constants.ADDR_AUDIO_SYSTEM)) {
    174             mAvrStatusCount = 0;
    175         }
    176 
    177         // Next, check added devices.
    178         BitSet added = complement(polledResult, currentInfos);
    179         index = -1;
    180         while ((index = added.nextSetBit(index + 1)) != -1) {
    181             Slog.v(TAG, "Add device by hot-plug detection:" + index);
    182             addDevice(index);
    183         }
    184     }
    185 
    186     private static BitSet infoListToBitSet(List<HdmiDeviceInfo> infoList, boolean audioOnly) {
    187         BitSet set = new BitSet(NUM_OF_ADDRESS);
    188         for (HdmiDeviceInfo info : infoList) {
    189             if (audioOnly) {
    190                 if (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
    191                     set.set(info.getLogicalAddress());
    192                 }
    193             } else {
    194                 set.set(info.getLogicalAddress());
    195             }
    196         }
    197         return set;
    198     }
    199 
    200     private static BitSet addressListToBitSet(List<Integer> list) {
    201         BitSet set = new BitSet(NUM_OF_ADDRESS);
    202         for (Integer value : list) {
    203             set.set(value);
    204         }
    205         return set;
    206     }
    207 
    208     // A - B = A & ~B
    209     private static BitSet complement(BitSet first, BitSet second) {
    210         // Need to clone it so that it doesn't touch original set.
    211         BitSet clone = (BitSet) first.clone();
    212         clone.andNot(second);
    213         return clone;
    214     }
    215 
    216     private void addDevice(int addedAddress) {
    217         // Sending <Give Physical Address> will initiate new device action.
    218         sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(),
    219                 addedAddress));
    220     }
    221 
    222     private void removeDevice(int removedAddress) {
    223         mayChangeRoutingPath(removedAddress);
    224         mayCancelDeviceSelect(removedAddress);
    225         mayCancelOneTouchRecord(removedAddress);
    226         mayDisableSystemAudioAndARC(removedAddress);
    227 
    228         tv().removeCecDevice(removedAddress);
    229     }
    230 
    231     private void mayChangeRoutingPath(int address) {
    232         HdmiDeviceInfo info = tv().getCecDeviceInfo(address);
    233         if (info != null) {
    234             tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress());
    235         }
    236     }
    237 
    238     private void mayCancelDeviceSelect(int address) {
    239         List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class);
    240         if (actions.isEmpty()) {
    241             return;
    242         }
    243 
    244         // Should have only one Device Select Action
    245         DeviceSelectAction action = actions.get(0);
    246         if (action.getTargetAddress() == address) {
    247             removeAction(DeviceSelectAction.class);
    248         }
    249     }
    250 
    251     private void mayCancelOneTouchRecord(int address) {
    252         List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
    253         for (OneTouchRecordAction action : actions) {
    254             if (action.getRecorderAddress() == address) {
    255                 removeAction(action);
    256             }
    257         }
    258     }
    259 
    260     private void mayDisableSystemAudioAndARC(int address) {
    261         if (HdmiUtils.getTypeFromAddress(address) != HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
    262             return;
    263         }
    264 
    265         tv().setSystemAudioMode(false);
    266         if (tv().isArcEstablished()) {
    267             tv().enableAudioReturnChannel(false);
    268             addAndStartAction(new RequestArcTerminationAction(localDevice(), address));
    269         }
    270     }
    271 }
    272