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                 ++mAvrStatusCount;
    160                 Slog.w(TAG, "Ack not returned from AVR. count: " + mAvrStatusCount);
    161                 if (mAvrStatusCount < AVR_COUNT_MAX) {
    162                     continue;
    163                 }
    164             }
    165             Slog.v(TAG, "Remove device by hot-plug detection:" + index);
    166             removeDevice(index);
    167         }
    168 
    169         // Reset the counter if the ack is returned from AVR.
    170         if (!removed.get(Constants.ADDR_AUDIO_SYSTEM)) {
    171             mAvrStatusCount = 0;
    172         }
    173 
    174         // Next, check added devices.
    175         BitSet added = complement(polledResult, currentInfos);
    176         index = -1;
    177         while ((index = added.nextSetBit(index + 1)) != -1) {
    178             Slog.v(TAG, "Add device by hot-plug detection:" + index);
    179             addDevice(index);
    180         }
    181     }
    182 
    183     private static BitSet infoListToBitSet(List<HdmiDeviceInfo> infoList, boolean audioOnly) {
    184         BitSet set = new BitSet(NUM_OF_ADDRESS);
    185         for (HdmiDeviceInfo info : infoList) {
    186             if (audioOnly) {
    187                 if (info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
    188                     set.set(info.getLogicalAddress());
    189                 }
    190             } else {
    191                 set.set(info.getLogicalAddress());
    192             }
    193         }
    194         return set;
    195     }
    196 
    197     private static BitSet addressListToBitSet(List<Integer> list) {
    198         BitSet set = new BitSet(NUM_OF_ADDRESS);
    199         for (Integer value : list) {
    200             set.set(value);
    201         }
    202         return set;
    203     }
    204 
    205     // A - B = A & ~B
    206     private static BitSet complement(BitSet first, BitSet second) {
    207         // Need to clone it so that it doesn't touch original set.
    208         BitSet clone = (BitSet) first.clone();
    209         clone.andNot(second);
    210         return clone;
    211     }
    212 
    213     private void addDevice(int addedAddress) {
    214         // Sending <Give Physical Address> will initiate new device action.
    215         sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(),
    216                 addedAddress));
    217     }
    218 
    219     private void removeDevice(int removedAddress) {
    220         mayChangeRoutingPath(removedAddress);
    221         mayCancelDeviceSelect(removedAddress);
    222         mayCancelOneTouchRecord(removedAddress);
    223         mayDisableSystemAudioAndARC(removedAddress);
    224 
    225         tv().removeCecDevice(removedAddress);
    226     }
    227 
    228     private void mayChangeRoutingPath(int address) {
    229         HdmiDeviceInfo info = tv().getCecDeviceInfo(address);
    230         if (info != null) {
    231             tv().handleRemoveActiveRoutingPath(info.getPhysicalAddress());
    232         }
    233     }
    234 
    235     private void mayCancelDeviceSelect(int address) {
    236         List<DeviceSelectAction> actions = getActions(DeviceSelectAction.class);
    237         if (actions.isEmpty()) {
    238             return;
    239         }
    240 
    241         // Should have only one Device Select Action
    242         DeviceSelectAction action = actions.get(0);
    243         if (action.getTargetAddress() == address) {
    244             removeAction(DeviceSelectAction.class);
    245         }
    246     }
    247 
    248     private void mayCancelOneTouchRecord(int address) {
    249         List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class);
    250         for (OneTouchRecordAction action : actions) {
    251             if (action.getRecorderAddress() == address) {
    252                 removeAction(action);
    253             }
    254         }
    255     }
    256 
    257     private void mayDisableSystemAudioAndARC(int address) {
    258         if (HdmiUtils.getTypeFromAddress(address) != HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
    259             return;
    260         }
    261 
    262         // Turn off system audio mode and update settings.
    263         tv().setSystemAudioMode(false, true);
    264         if (tv().isArcEstabilished()) {
    265             addAndStartAction(new RequestArcTerminationAction(localDevice(), address));
    266         }
    267     }
    268 }
    269