TvInputHardwareManager.java revision 839ae5f460caadf8580b7e0ab77e255d7a1ddae5
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 17package com.android.server.tv; 18 19import android.content.Context; 20import android.media.AudioDevicePort; 21import android.media.AudioManager; 22import android.media.AudioPatch; 23import android.media.AudioPort; 24import android.media.AudioPortConfig; 25import android.media.tv.ITvInputHardware; 26import android.media.tv.ITvInputHardwareCallback; 27import android.media.tv.TvInputHardwareInfo; 28import android.media.tv.TvStreamConfig; 29import android.os.IBinder; 30import android.os.RemoteException; 31import android.util.Slog; 32import android.util.SparseArray; 33import android.view.KeyEvent; 34import android.view.Surface; 35 36import java.util.ArrayList; 37import java.util.HashSet; 38import java.util.List; 39import java.util.Set; 40 41/** 42 * A helper class for TvInputManagerService to handle TV input hardware. 43 * 44 * This class does a basic connection management and forwarding calls to TvInputHal which eventually 45 * calls to tv_input HAL module. 46 * 47 * @hide 48 */ 49class TvInputHardwareManager implements TvInputHal.Callback { 50 private static final String TAG = TvInputHardwareManager.class.getSimpleName(); 51 private final TvInputHal mHal = new TvInputHal(this); 52 private final SparseArray<Connection> mConnections = new SparseArray<Connection>(); 53 private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>(); 54 private final Context mContext; 55 private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>(); 56 private final AudioManager mAudioManager; 57 58 private final Object mLock = new Object(); 59 60 public TvInputHardwareManager(Context context) { 61 mContext = context; 62 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 63 // TODO(hdmi): mHdmiManager = mContext.getSystemService(...); 64 // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient(); 65 mHal.init(); 66 } 67 68 @Override 69 public void onDeviceAvailable( 70 TvInputHardwareInfo info, TvStreamConfig[] configs) { 71 synchronized (mLock) { 72 Connection connection = new Connection(info); 73 connection.updateConfigsLocked(configs); 74 mConnections.put(info.getDeviceId(), connection); 75 buildInfoListLocked(); 76 // TODO: notify if necessary 77 } 78 } 79 80 private void buildInfoListLocked() { 81 mInfoList.clear(); 82 for (int i = 0; i < mConnections.size(); ++i) { 83 mInfoList.add(mConnections.valueAt(i).getInfoLocked()); 84 } 85 } 86 87 @Override 88 public void onDeviceUnavailable(int deviceId) { 89 synchronized (mLock) { 90 Connection connection = mConnections.get(deviceId); 91 if (connection == null) { 92 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId); 93 return; 94 } 95 connection.resetLocked(null, null, null, null); 96 mConnections.remove(deviceId); 97 buildInfoListLocked(); 98 // TODO: notify if necessary 99 } 100 } 101 102 @Override 103 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) { 104 synchronized (mLock) { 105 Connection connection = mConnections.get(deviceId); 106 if (connection == null) { 107 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with " 108 + deviceId); 109 return; 110 } 111 connection.updateConfigsLocked(configs); 112 try { 113 connection.getCallbackLocked().onStreamConfigChanged(configs); 114 } catch (RemoteException e) { 115 Slog.e(TAG, "onStreamConfigurationChanged: " + e); 116 } 117 } 118 } 119 120 public List<TvInputHardwareInfo> getHardwareList() { 121 synchronized (mLock) { 122 return mInfoList; 123 } 124 } 125 126 private boolean checkUidChangedLocked( 127 Connection connection, int callingUid, int resolvedUserId) { 128 Integer connectionCallingUid = connection.getCallingUidLocked(); 129 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked(); 130 if (connectionCallingUid == null || connectionResolvedUserId == null) { 131 return true; 132 } 133 if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) { 134 return true; 135 } 136 return false; 137 } 138 139 /** 140 * Create a TvInputHardware object with a specific deviceId. One service at a time can access 141 * the object, and if more than one process attempts to create hardware with the same deviceId, 142 * the latest service will get the object and all the other hardware are released. The 143 * release is notified via ITvInputHardwareCallback.onReleased(). 144 */ 145 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback, 146 int callingUid, int resolvedUserId) { 147 if (callback == null) { 148 throw new NullPointerException(); 149 } 150 synchronized (mLock) { 151 Connection connection = mConnections.get(deviceId); 152 if (connection == null) { 153 Slog.e(TAG, "Invalid deviceId : " + deviceId); 154 return null; 155 } 156 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 157 TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked()); 158 try { 159 callback.asBinder().linkToDeath(connection, 0); 160 } catch (RemoteException e) { 161 hardware.release(); 162 return null; 163 } 164 connection.resetLocked(hardware, callback, callingUid, resolvedUserId); 165 } 166 return connection.getHardwareLocked(); 167 } 168 } 169 170 /** 171 * Release the specified hardware. 172 */ 173 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid, 174 int resolvedUserId) { 175 synchronized (mLock) { 176 Connection connection = mConnections.get(deviceId); 177 if (connection == null) { 178 Slog.e(TAG, "Invalid deviceId : " + deviceId); 179 return; 180 } 181 if (connection.getHardwareLocked() != hardware 182 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 183 return; 184 } 185 connection.resetLocked(null, null, null, null); 186 } 187 } 188 189 private class Connection implements IBinder.DeathRecipient { 190 private final TvInputHardwareInfo mInfo; 191 private TvInputHardwareImpl mHardware = null; 192 private ITvInputHardwareCallback mCallback; 193 private TvStreamConfig[] mConfigs = null; 194 private Integer mCallingUid = null; 195 private Integer mResolvedUserId = null; 196 197 public Connection(TvInputHardwareInfo info) { 198 mInfo = info; 199 } 200 201 // *Locked methods assume TvInputHardwareManager.mLock is held. 202 203 public void resetLocked(TvInputHardwareImpl hardware, 204 ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) { 205 if (mHardware != null) { 206 try { 207 mCallback.onReleased(); 208 } catch (RemoteException e) { 209 Slog.e(TAG, "Connection::resetHardware: " + e); 210 } 211 mHardware.release(); 212 } 213 mHardware = hardware; 214 mCallback = callback; 215 mCallingUid = callingUid; 216 mResolvedUserId = resolvedUserId; 217 218 if (mHardware != null && mCallback != null) { 219 try { 220 mCallback.onStreamConfigChanged(getConfigsLocked()); 221 } catch (RemoteException e) { 222 Slog.e(TAG, "Connection::resetHardware: " + e); 223 } 224 } 225 } 226 227 public void updateConfigsLocked(TvStreamConfig[] configs) { 228 mConfigs = configs; 229 } 230 231 public TvInputHardwareInfo getInfoLocked() { 232 return mInfo; 233 } 234 235 public ITvInputHardware getHardwareLocked() { 236 return mHardware; 237 } 238 239 public ITvInputHardwareCallback getCallbackLocked() { 240 return mCallback; 241 } 242 243 public TvStreamConfig[] getConfigsLocked() { 244 return mConfigs; 245 } 246 247 public Integer getCallingUidLocked() { 248 return mCallingUid; 249 } 250 251 public Integer getResolvedUserIdLocked() { 252 return mResolvedUserId; 253 } 254 255 @Override 256 public void binderDied() { 257 synchronized (mLock) { 258 resetLocked(null, null, null, null); 259 } 260 } 261 } 262 263 private class TvInputHardwareImpl extends ITvInputHardware.Stub { 264 private final TvInputHardwareInfo mInfo; 265 private boolean mReleased = false; 266 private final Object mImplLock = new Object(); 267 268 private final AudioDevicePort mAudioSource; 269 private final AudioDevicePort mAudioSink; 270 private AudioPatch mAudioPatch = null; 271 272 private TvStreamConfig mActiveConfig = null; 273 274 public TvInputHardwareImpl(TvInputHardwareInfo info) { 275 mInfo = info; 276 AudioDevicePort audioSource = null; 277 AudioDevicePort audioSink = null; 278 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) { 279 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 280 if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) { 281 // Find source 282 for (AudioPort port : devicePorts) { 283 AudioDevicePort devicePort = (AudioDevicePort) port; 284 if (devicePort.type() == mInfo.getAudioType() && 285 devicePort.address().equals(mInfo.getAudioAddress())) { 286 audioSource = devicePort; 287 break; 288 } 289 } 290 // Find sink 291 // TODO: App may want to specify sink device? 292 int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); 293 for (AudioPort port : devicePorts) { 294 AudioDevicePort devicePort = (AudioDevicePort) port; 295 if (devicePort.type() == sinkDevices) { 296 audioSink = devicePort; 297 break; 298 } 299 } 300 } 301 } 302 mAudioSource = audioSource; 303 mAudioSink = audioSink; 304 } 305 306 public void release() { 307 synchronized (mImplLock) { 308 if (mAudioPatch != null) { 309 mAudioManager.releaseAudioPatch(mAudioPatch); 310 mAudioPatch = null; 311 } 312 mReleased = true; 313 } 314 } 315 316 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client 317 // attempts to call setSurface with different TvStreamConfig objects, the last call will 318 // prevail. 319 @Override 320 public boolean setSurface(Surface surface, TvStreamConfig config) 321 throws RemoteException { 322 synchronized (mImplLock) { 323 if (mReleased) { 324 throw new IllegalStateException("Device already released."); 325 } 326 if (surface != null && config == null) { 327 return false; 328 } 329 if (surface == null && mActiveConfig == null) { 330 return false; 331 } 332 if (mInfo.getType() == TvInputHal.TYPE_HDMI) { 333 if (surface != null) { 334 // Set "Active Source" for HDMI. 335 // TODO(hdmi): mHdmiClient.deviceSelect(...); 336 mActiveHdmiSources.add(mInfo.getDeviceId()); 337 } else { 338 mActiveHdmiSources.remove(mInfo.getDeviceId()); 339 if (mActiveHdmiSources.size() == 0) { 340 // Tell HDMI that no HDMI source is active 341 // TODO(hdmi): mHdmiClient.portSelect(null); 342 } 343 } 344 } 345 if (mAudioSource != null && mAudioSink != null) { 346 if (surface != null) { 347 AudioPortConfig sourceConfig = mAudioSource.activeConfig(); 348 AudioPortConfig sinkConfig = mAudioSink.activeConfig(); 349 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; 350 // TODO: build config if activeConfig() == null 351 mAudioManager.createAudioPatch( 352 audioPatchArray, 353 new AudioPortConfig[] { sourceConfig }, 354 new AudioPortConfig[] { sinkConfig }); 355 mAudioPatch = audioPatchArray[0]; 356 } else { 357 mAudioManager.releaseAudioPatch(mAudioPatch); 358 mAudioPatch = null; 359 } 360 } 361 int result = TvInputHal.ERROR_UNKNOWN; 362 if (surface == null) { 363 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 364 mActiveConfig = null; 365 } else { 366 if (config != mActiveConfig && mActiveConfig != null) { 367 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 368 if (result != TvInputHal.SUCCESS) { 369 mActiveConfig = null; 370 return false; 371 } 372 } 373 result = mHal.addStream(mInfo.getDeviceId(), surface, config); 374 if (result == TvInputHal.SUCCESS) { 375 mActiveConfig = config; 376 } 377 } 378 return result == TvInputHal.SUCCESS; 379 } 380 } 381 382 @Override 383 public void setVolume(float volume) throws RemoteException { 384 synchronized (mImplLock) { 385 if (mReleased) { 386 throw new IllegalStateException("Device already released."); 387 } 388 } 389 // TODO: Use AudioGain? 390 } 391 392 @Override 393 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { 394 synchronized (mImplLock) { 395 if (mReleased) { 396 throw new IllegalStateException("Device already released."); 397 } 398 } 399 if (mInfo.getType() != TvInputHal.TYPE_HDMI) { 400 return false; 401 } 402 // TODO(hdmi): mHdmiClient.sendKeyEvent(event); 403 return false; 404 } 405 } 406} 407