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                 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) &&
    232                         ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_OSD_NAME)) {
    233                     handleSetOsdName(cmd);
    234                     return true;
    235                 }
    236                 return false;
    237             case STATE_WAITING_FOR_VENDOR_ID:
    238                 if (cmd.getOpcode() == Constants.MESSAGE_DEVICE_VENDOR_ID) {
    239                     handleVendorId(cmd);
    240                     return true;
    241                 } else if ((cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) &&
    242                         ((cmd.getParams()[0] & 0xFF) == Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID)) {
    243                     handleVendorId(cmd);
    244                     return true;
    245                 }
    246                 return false;
    247             case STATE_WAITING_FOR_DEVICE_POLLING:
    248                 // Fall through.
    249             default:
    250                 return false;
    251         }
    252     }
    253 
    254     private void handleReportPhysicalAddress(HdmiCecMessage cmd) {
    255         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
    256 
    257         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
    258         if (current.mLogicalAddress != cmd.getSource()) {
    259             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
    260                     cmd.getSource());
    261             return;
    262         }
    263 
    264         byte params[] = cmd.getParams();
    265         current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
    266         current.mPortId = getPortId(current.mPhysicalAddress);
    267         current.mDeviceType = params[2] & 0xFF;
    268 
    269         tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
    270                     current.mPhysicalAddress);
    271 
    272         increaseProcessedDeviceCount();
    273         checkAndProceedStage();
    274     }
    275 
    276     private int getPortId(int physicalAddress) {
    277         return tv().getPortId(physicalAddress);
    278     }
    279 
    280     private void handleSetOsdName(HdmiCecMessage cmd) {
    281         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
    282 
    283         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
    284         if (current.mLogicalAddress != cmd.getSource()) {
    285             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
    286                     cmd.getSource());
    287             return;
    288         }
    289 
    290         String displayName = null;
    291         try {
    292             if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) {
    293                 displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
    294             } else {
    295                 displayName = new String(cmd.getParams(), "US-ASCII");
    296             }
    297         } catch (UnsupportedEncodingException e) {
    298             Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
    299             // If failed to get display name, use the default name of device.
    300             displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
    301         }
    302         current.mDisplayName = displayName;
    303         increaseProcessedDeviceCount();
    304         checkAndProceedStage();
    305     }
    306 
    307     private void handleVendorId(HdmiCecMessage cmd) {
    308         Preconditions.checkState(mProcessedDeviceCount < mDevices.size());
    309 
    310         DeviceInfo current = mDevices.get(mProcessedDeviceCount);
    311         if (current.mLogicalAddress != cmd.getSource()) {
    312             Slog.w(TAG, "Unmatched address[expected:" + current.mLogicalAddress + ", actual:" +
    313                     cmd.getSource());
    314             return;
    315         }
    316 
    317         if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
    318             byte[] params = cmd.getParams();
    319             int vendorId = HdmiUtils.threeBytesToInt(params);
    320             current.mVendorId = vendorId;
    321         }
    322 
    323         increaseProcessedDeviceCount();
    324         checkAndProceedStage();
    325     }
    326 
    327     private void increaseProcessedDeviceCount() {
    328         mProcessedDeviceCount++;
    329         mTimeoutRetry = 0;
    330     }
    331 
    332     private void removeDevice(int index) {
    333         mDevices.remove(index);
    334     }
    335 
    336     private void wrapUpAndFinish() {
    337         Slog.v(TAG, "---------Wrap up Device Discovery:[" + mDevices.size() + "]---------");
    338         ArrayList<HdmiDeviceInfo> result = new ArrayList<>();
    339         for (DeviceInfo info : mDevices) {
    340             HdmiDeviceInfo cecDeviceInfo = info.toHdmiDeviceInfo();
    341             Slog.v(TAG, " DeviceInfo: " + cecDeviceInfo);
    342             result.add(cecDeviceInfo);
    343         }
    344         Slog.v(TAG, "--------------------------------------------");
    345         mCallback.onDeviceDiscoveryDone(result);
    346         finish();
    347         // Process any commands buffered while device discovery action was in progress.
    348         tv().processAllDelayedMessages();
    349     }
    350 
    351     private void checkAndProceedStage() {
    352         if (mDevices.isEmpty()) {
    353             wrapUpAndFinish();
    354             return;
    355         }
    356 
    357         // If finished current stage, move on to next stage.
    358         if (mProcessedDeviceCount == mDevices.size()) {
    359             mProcessedDeviceCount = 0;
    360             switch (mState) {
    361                 case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
    362                     startOsdNameStage();
    363                     return;
    364                 case STATE_WAITING_FOR_OSD_NAME:
    365                     startVendorIdStage();
    366                     return;
    367                 case STATE_WAITING_FOR_VENDOR_ID:
    368                     wrapUpAndFinish();
    369                     return;
    370                 default:
    371                     return;
    372             }
    373         } else {
    374             sendQueryCommand();
    375         }
    376     }
    377 
    378     private void sendQueryCommand() {
    379         int address = mDevices.get(mProcessedDeviceCount).mLogicalAddress;
    380         switch (mState) {
    381             case STATE_WAITING_FOR_PHYSICAL_ADDRESS:
    382                 queryPhysicalAddress(address);
    383                 return;
    384             case STATE_WAITING_FOR_OSD_NAME:
    385                 queryOsdName(address);
    386                 return;
    387             case STATE_WAITING_FOR_VENDOR_ID:
    388                 queryVendorId(address);
    389             default:
    390                 return;
    391         }
    392     }
    393 
    394     @Override
    395     void handleTimerEvent(int state) {
    396         if (mState == STATE_NONE || mState != state) {
    397             return;
    398         }
    399 
    400         if (++mTimeoutRetry < HdmiConfig.TIMEOUT_RETRY) {
    401             sendQueryCommand();
    402             return;
    403         }
    404         mTimeoutRetry = 0;
    405         Slog.v(TAG, "Timeout[State=" + mState + ", Processed=" + mProcessedDeviceCount);
    406         removeDevice(mProcessedDeviceCount);
    407         checkAndProceedStage();
    408     }
    409 }
    410