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.internal.util.Preconditions;
     23 import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
     24 
     25 import java.io.UnsupportedEncodingException;
     26 import java.util.ArrayList;
     27 import java.util.List;
     28 
     29 /**
     30  * Feature action that handles device discovery sequences.
     31  * Device discovery is launched when TV device is woken from "Standby" state
     32  * or enabled "Control for Hdmi" from disabled state.
     33  *
     34  * <p>Device discovery goes through the following steps.
     35  * <ol>
     36  *   <li>Poll all non-local devices by sending &lt;Polling Message&gt;
     37  *   <li>Gather "Physical address" and "device type" of all acknowledged devices
     38  *   <li>Gather "OSD (display) name" of all acknowledge devices
     39  *   <li>Gather "Vendor id" of all acknowledge devices
     40  * </ol>
     41  * We attempt to get OSD name/vendor ID up to 5 times in case the communication fails.
     42  */
     43 final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
     44     private static final String TAG = "DeviceDiscoveryAction";
     45 
     46     // State in which the action is waiting for device polling.
     47     private static final int STATE_WAITING_FOR_DEVICE_POLLING = 1;
     48     // State in which the action is waiting for gathering physical address of non-local devices.
     49     private static final int STATE_WAITING_FOR_PHYSICAL_ADDRESS = 2;
     50     // State in which the action is waiting for gathering osd name of non-local devices.
     51     private static final int STATE_WAITING_FOR_OSD_NAME = 3;
     52     // State in which the action is waiting for gathering vendor id of non-local devices.
     53     private static final int STATE_WAITING_FOR_VENDOR_ID = 4;
     54 
     55     /**
     56      * Interface used to report result of device discovery.
     57      */
     58     interface DeviceDiscoveryCallback {
     59         /**
     60          * Called when device discovery is done.
     61          *
     62          * @param deviceInfos a list of all non-local devices. It can be empty list.
     63          */
     64         void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos);
     65     }
     66 
     67     // An internal container used to keep track of device information during
     68     // this action.
     69     private static final class DeviceInfo {
     70         private final int mLogicalAddress;
     71 
     72         private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
     73         private int mPortId = Constants.INVALID_PORT_ID;
     74         private int mVendorId = Constants.UNKNOWN_VENDOR_ID;
     75         private String mDisplayName = "";
     76         private int mDeviceType = HdmiDeviceInfo.DEVICE_INACTIVE;
     77 
     78         private DeviceInfo(int logicalAddress) {
     79             mLogicalAddress = logicalAddress;
     80         }
     81 
     82         private HdmiDeviceInfo toHdmiDeviceInfo() {
     83             return new HdmiDeviceInfo(mLogicalAddress, mPhysicalAddress, mPortId, mDeviceType,
     84                     mVendorId, mDisplayName);
     85         }
     86     }
     87 
     88     private final ArrayList<DeviceInfo> mDevices = new ArrayList<>();
     89     private final DeviceDiscoveryCallback mCallback;
     90     private int mProcessedDeviceCount = 0;
     91     private int mTimeoutRetry = 0;
     92 
     93     /**
     94      * Constructor.
     95      *
     96      * @param source an instance of {@link HdmiCecLocalDevice}.
     97      */
     98     DeviceDiscoveryAction(HdmiCecLocalDevice source, DeviceDiscoveryCallback callback) {
     99         super(source);
    100         mCallback = Preconditions.checkNotNull(callback);
    101     }
    102 
    103     @Override
    104     boolean start() {
    105         mDevices.clear();
    106         mState = STATE_WAITING_FOR_DEVICE_POLLING;
    107 
    108         pollDevices(new DevicePollingCallback() {
    109             @Override
    110             public void onPollingFinished(List<Integer> ackedAddress) {
    111                 if (ackedAddress.isEmpty()) {
    112                     Slog.v(TAG, "No device is detected.");
    113                     wrapUpAndFinish();
    114                     return;
    115                 }
    116 
    117                 Slog.v(TAG, "Device detected: " + ackedAddress);
    118                 allocateDevices(ackedAddress);
    119                 startPhysicalAddressStage();
    120             }
    121         }, Constants.POLL_ITERATION_REVERSE_ORDER
    122             | Constants.POLL_STRATEGY_REMOTES_DEVICES, HdmiConfig.DEVICE_POLLING_RETRY);
    123         return true;
    124     }
    125 
    126     private void allocateDevices(List<Integer> addresses) {
    127         for (Integer i : addresses) {
    128             DeviceInfo info = new DeviceInfo(i);
    129             mDevices.add(info);
    130         }
    131     }
    132 
    133     private void startPhysicalAddressStage() {
    134         Slog.v(TAG, "Start [Physical Address Stage]:" + mDevices.size());
    135         mProcessedDeviceCount = 0;
    136         mState = STATE_WAITING_FOR_PHYSICAL_ADDRESS;
    137 
    138         checkAndProceedStage();
    139     }
    140 
    141     private boolean verifyValidLogicalAddress(int address) {
    142         return address >= Constants.ADDR_TV && address < Constants.ADDR_UNREGISTERED;
    143     }
    144 
    145     private void queryPhysicalAddress(int address) {
    146         if (!verifyValidLogicalAddress(address)) {
    147             checkAndProceedStage();
    148             return;
    149         }
    150 
    151         mActionTimer.clearTimerMessage();
    152 
    153         // Check cache first and send request if not exist.
    154         if (mayProcessMessageIfCached(address, Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS)) {
    155             return;
    156         }
    157         sendCommand(HdmiCecMessageBuilder.buildGivePhysicalAddress(getSourceAddress(), address));
    158         addTimer(mState, HdmiConfig.TIMEOUT_MS);
    159     }
    160 
    161     private void startOsdNameStage() {
    162         Slog.v(TAG, "Start [Osd Name Stage]:" + mDevices.size());
    163         mProcessedDeviceCount = 0;
    164         mState = STATE_WAITING_FOR_OSD_NAME;
    165 
    166         checkAndProceedStage();
    167     }
    168 
    169     private void queryOsdName(int address) {
    170         if (!verifyValidLogicalAddress(address)) {
    171             checkAndProceedStage();
    172             return;
    173         }
    174 
    175         mActionTimer.clearTimerMessage();
    176 
    177         if (mayProcessMessageIfCached(address, Constants.MESSAGE_SET_OSD_NAME)) {
    178             return;
    179         }
    180         sendCommand(HdmiCecMessageBuilder.buildGiveOsdNameCommand(getSourceAddress(), address));
    181         addTimer(mState, HdmiConfig.TIMEOUT_MS);
    182     }
    183 
    184     private void startVendorIdStage() {
    185         Slog.v(TAG, "Start [Vendor Id Stage]:" + mDevices.size());
    186 
    187         mProcessedDeviceCount = 0;
    188         mState = STATE_WAITING_FOR_VENDOR_ID;
    189 
    190         checkAndProceedStage();
    191     }
    192 
    193     private void queryVendorId(int address) {
    194         if (!verifyValidLogicalAddress(address)) {
    195             checkAndProceedStage();
    196             return;
    197         }
    198 
    199         mActionTimer.clearTimerMessage();
    200 
    201         if (mayProcessMessageIfCached(address, Constants.MESSAGE_DEVICE_VENDOR_ID)) {
    202             return;
    203         }
    204         sendCommand(
    205                 HdmiCecMessageBuilder.buildGiveDeviceVendorIdCommand(getSourceAddress(), address));
    206         addTimer(mState, HdmiConfig.TIMEOUT_MS);
    207     }
    208 
    209     private boolean mayProcessMessageIfCached(int address, int opcode) {
    210         HdmiCecMessage message = getCecMessageCache().getMessage(address, opcode);
    211         if (message != null) {
    212             processCommand(message);
    213             return true;
    214         }
    215         return false;
    216     }
    217 
    218     @Override
    219     boolean processCommand(HdmiCecMessage cmd) {
    220         switch (mState) {
    221             case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
    222                 if (cmd.getOpcode() == Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS) {
    223                     handleReportPhysicalAddress(cmd);
    224                     return true;
    225                 }
    226                 return false;
    227             case STATE_WAITING_FOR_OSD_NAME:
    228                 if (cmd.getOpcode() == Constants.MESSAGE_SET_OSD_NAME) {
    229                     handleSetOsdName(cmd);
    230                     return true;
    231                 }
    232                 return false;
    233             case STATE_WAITING_FOR_VENDOR_ID:
    234                 if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) {
    235                     handleVendorId(cmd);
    236                     return true;
    237                 }
    238                 return false;
    239             case STATE_WAITING_FOR_DEVICE_POLLING:
    240                 // Fall through.
    241             default:
    242                 return false;
    243         }
    244     }
    245 
    246     private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
    247         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
    248 
    249         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
    250         if (current.mLogicalAddress != cmd.getSource()) {
    251             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
    252                     cmd.getSource());
    253             return;
    254         }
    255 
    256         byte params[] = cmd.getParams();
    257         current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
    258         current.mPortId = getPortId(current.mPhysicalAddress);
    259         current.mDeviceType = params[2] & 0xFF;
    260 
    261         tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
    262                     current.mPhysicalAddress);
    263 
    264         increaseProcessedDeviceCount();
    265         checkAndProceedStage();
    266     }
    267 
    268     private int getPortId(int physicalAddress) {
    269         return tv().getPortId(physicalAddress);
    270     }
    271 
    272     private void handleSetOsdName(HdmiCecMessage cmd) {
    273         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
    274 
    275         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
    276         if (current.mLogicalAddress != cmd.getSource()) {
    277             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
    278                     cmd.getSource());
    279             return;
    280         }
    281 
    282         String displayName = null;
    283         try {
    284             displayName = new String(cmd.getParams(), "US-ASCII");
    285         } catch (UnsupportedEncodingException e) {
    286             Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
    287             // If failed to get display name, use the default name of device.
    288             displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
    289         }
    290         current.mDisplayName = displayName;
    291         increaseProcessedDeviceCount();
    292         checkAndProceedStage();
    293     }
    294 
    295     private void handleVendorId(HdmiCecMessage cmd) {
    296         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
    297 
    298         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
    299         if (current.mLogicalAddress != cmd.getSource()) {
    300             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
    301                     cmd.getSource());
    302             return;
    303         }
    304 
    305         byte[] params = cmd.getParams();
    306         int vendorId = HdmiUtils.threeBytesToInt(params);
    307         current.mVendorId = vendorId;
    308         increaseProcessedDeviceCount();
    309         checkAndProceedStage();
    310     }
    311 
    312     private void increaseProcessedDeviceCount() {
    313         mProcessedDeviceCount++;
    314         mTimeoutRetry = 0;
    315     }
    316 
    317     private void removeDevice(int index) {
    318         mDevices.remove(index);
    319     }
    320 
    321     private void wrapUpAndFinish() {
    322         Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------");
    323         ArrayList<HdmiDeviceInfo> result = new ArrayList<>();
    324         for (DeviceInfo info : mDevices) {
    325             HdmiDeviceInfo cecDeviceInfo = info.toHdmiDeviceInfo();
    326             Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo);
    327             result.add(cecDeviceInfo);
    328         }
    329         Slog.v(TAG, "--------------------------------------------");
    330         mCallback.onDeviceDiscoveryDone(result);
    331         finish();
    332         // Process any commands buffered while device discovery action was in progress.
    333         tv().processAllDelayedMessages();
    334     }
    335 
    336     private void checkAndProceedStage() {
    337         if (mDevices.isEmpty()) {
    338             wrapUpAndFinish();
    339             return;
    340         }
    341 
    342         // If finished current stage, move on to next stage.
    343         if (mProcessedDeviceCount == mDevices.size()) {
    344             mProcessedDeviceCount = 0;
    345             switch (mState) {
    346                 case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
    347                     startOsdNameStage();
    348                     return;
    349                 case STATE_WAITING_FOR_OSD_NAME:
    350                     startVendorIdStage();
    351                     return;
    352                 case STATE_WAITING_FOR_VENDOR_ID:
    353                     wrapUpAndFinish();
    354                     return;
    355                 default:
    356                     return;
    357             }
    358         } else {
    359             sendQueryCommand();
    360         }
    361     }
    362 
    363     private void sendQueryCommand() {
    364         int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
    365         switch (mState) {
    366             case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
    367                 queryPhysicalAddress(address);
    368                 return;
    369             case STATE_WAITING_FOR_OSD_NAME:
    370                 queryOsdName(address);
    371                 return;
    372             case STATE_WAITING_FOR_VENDOR_ID:
    373                 queryVendorId(address);
    374             default:
    375                 return;
    376         }
    377     }
    378 
    379     @Override
    380     void handleTimerEvent(int state) {
    381         if (mState == STATE_NONE || mState != state) {
    382             return;
    383         }
    384 
    385         if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
    386             sendQueryCommand();
    387             return;
    388         }
    389         mTimeoutRetry = 0;
    390         Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
    391         removeDevice(mProcessedDeviceCount);
    392         checkAndProceedStage();
    393     }
    394 }
    395