summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJinsuk Kim <jinsukkim@google.com>2014-05-22 03:19:23 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-05-22 03:19:23 +0000
commitdbbceffd9b7fc812da9a5b0a1269d43f748c37b8 (patch)
tree0e1517ae26a520f8d84ae121c86f4e2659141864
parent0c88b340acb699f125e3f2f8bdacc1409138a82d (diff)
parent78d695d8ba532214b02e7f18e0ccf89cf099163d (diff)
downloadframeworks_base-dbbceffd9b7fc812da9a5b0a1269d43f748c37b8.zip
frameworks_base-dbbceffd9b7fc812da9a5b0a1269d43f748c37b8.tar.gz
frameworks_base-dbbceffd9b7fc812da9a5b0a1269d43f748c37b8.tar.bz2
Merge "Add feature actions for HDMI-CEC playback device"
-rw-r--r--Android.mk1
-rw-r--r--api/current.txt15
-rw-r--r--core/java/android/hardware/hdmi/HdmiControlManager.java87
-rw-r--r--core/java/android/hardware/hdmi/HdmiHotplugEvent.aidl19
-rw-r--r--core/java/android/hardware/hdmi/HdmiHotplugEvent.java100
-rw-r--r--core/java/android/hardware/hdmi/IHdmiControlService.aidl8
-rw-r--r--core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl29
-rw-r--r--services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java105
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java41
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java129
-rw-r--r--services/core/java/com/android/server/hdmi/OneTouchPlayAction.java137
11 files changed, 664 insertions, 7 deletions
diff --git a/Android.mk b/Android.mk
index 2e78969..4f50dac 100644
--- a/Android.mk
+++ b/Android.mk
@@ -149,6 +149,7 @@ LOCAL_SRC_FILES += \
core/java/android/hardware/hdmi/IHdmiCecService.aidl \
core/java/android/hardware/hdmi/IHdmiControlCallback.aidl \
core/java/android/hardware/hdmi/IHdmiControlService.aidl \
+ core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl \
core/java/android/hardware/input/IInputManager.aidl \
core/java/android/hardware/input/IInputDevicesChangedListener.aidl \
core/java/android/hardware/location/IFusedLocationHardware.aidl \
diff --git a/api/current.txt b/api/current.txt
index f2240e0..4a32b83 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12865,8 +12865,23 @@ package android.hardware.hdmi {
}
public final class HdmiControlManager {
+ method public void addHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
method public android.hardware.hdmi.HdmiPlaybackClient getPlaybackClient();
method public android.hardware.hdmi.HdmiTvClient getTvClient();
+ method public void removeHotplugeEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
+ }
+
+ public static abstract interface HdmiControlManager.HotplugEventListener {
+ method public abstract void onReceived(android.hardware.hdmi.HdmiHotplugEvent);
+ }
+
+ public final class HdmiHotplugEvent implements android.os.Parcelable {
+ ctor public HdmiHotplugEvent(int, boolean);
+ method public int describeContents();
+ method public int getPort();
+ method public boolean isConnected();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
}
public final class HdmiPlaybackClient {
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index a3f27b9..f3de547 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -17,6 +17,8 @@
package android.hardware.hdmi;
import android.annotation.Nullable;
+import android.os.RemoteException;
+
/**
* The {@link HdmiControlManager} class is used to send HDMI control messages
* to attached CEC devices.
@@ -30,6 +32,11 @@ import android.annotation.Nullable;
public final class HdmiControlManager {
@Nullable private final IHdmiControlService mService;
+ // True if we have a logical device of type playback hosted in the system.
+ private final boolean mHasPlaybackDevice;
+ // True if we have a logical device of type TV hosted in the system.
+ private final boolean mHasTvDevice;
+
/**
* @hide - hide this constructor because it has a parameter of type
* IHdmiControlService, which is a system private class. The right way
@@ -38,6 +45,28 @@ public final class HdmiControlManager {
*/
public HdmiControlManager(IHdmiControlService service) {
mService = service;
+ int[] types = null;
+ if (mService != null) {
+ try {
+ types = mService.getSupportedTypes();
+ } catch (RemoteException e) {
+ // Do nothing.
+ }
+ }
+ mHasTvDevice = hasDeviceType(types, HdmiCec.DEVICE_TV);
+ mHasPlaybackDevice = hasDeviceType(types, HdmiCec.DEVICE_PLAYBACK);
+ }
+
+ private static boolean hasDeviceType(int[] types, int type) {
+ if (types == null) {
+ return false;
+ }
+ for (int t : types) {
+ if (t == type) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -51,7 +80,7 @@ public final class HdmiControlManager {
*/
@Nullable
public HdmiPlaybackClient getPlaybackClient() {
- if (mService == null) {
+ if (mService == null || !mHasPlaybackDevice) {
return null;
}
return new HdmiPlaybackClient(mService);
@@ -68,9 +97,61 @@ public final class HdmiControlManager {
*/
@Nullable
public HdmiTvClient getTvClient() {
- if (mService == null) {
- return null;
+ if (mService == null || !mHasTvDevice) {
+ return null;
}
return new HdmiTvClient(mService);
}
+
+ /**
+ * Listener used to get hotplug event from HDMI port.
+ */
+ public interface HotplugEventListener {
+ void onReceived(HdmiHotplugEvent event);
+ }
+
+ /**
+ * Adds a listener to get informed of {@link HdmiHotplugEvent}.
+ *
+ * <p>To stop getting the notification,
+ * use {@link #removeHotplugeEventListener(HotplugEventListener)}.
+ *
+ * @param listener {@link HotplugEventListener} instance
+ * @see HdmiControlManager#removeHotplugeEventListener(HotplugEventListener)
+ */
+ public void addHotplugEventListener(HotplugEventListener listener) {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.addHotplugEventListener(getHotplugEventListenerWrapper(listener));
+ } catch (RemoteException e) {
+ // Do nothing.
+ }
+ }
+
+ /**
+ * Removes a listener to stop getting informed of {@link HdmiHotplugEvent}.
+ *
+ * @param listener {@link HotplugEventListener} instance to be removed
+ */
+ public void removeHotplugeEventListener(HotplugEventListener listener) {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.removeHotplugEventListener(getHotplugEventListenerWrapper(listener));
+ } catch (RemoteException e) {
+ // Do nothing.
+ }
+ }
+
+ private IHdmiHotplugEventListener getHotplugEventListenerWrapper(
+ final HotplugEventListener listener) {
+ return new IHdmiHotplugEventListener.Stub() {
+ public void onReceived(HdmiHotplugEvent event) {
+ listener.onReceived(event);;
+ }
+ };
+ }
}
diff --git a/core/java/android/hardware/hdmi/HdmiHotplugEvent.aidl b/core/java/android/hardware/hdmi/HdmiHotplugEvent.aidl
new file mode 100644
index 0000000..3117dd6
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiHotplugEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.hdmi;
+
+parcelable HdmiHotplugEvent;
diff --git a/core/java/android/hardware/hdmi/HdmiHotplugEvent.java b/core/java/android/hardware/hdmi/HdmiHotplugEvent.java
new file mode 100644
index 0000000..1462f83
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiHotplugEvent.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.hdmi;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that describes the HDMI port hotplug event.
+ */
+public final class HdmiHotplugEvent implements Parcelable {
+
+ private final int mPort;
+ private final boolean mConnected;
+
+ /**
+ * Constructor.
+ *
+ * <p>Marked as hidden so only system can create the instance.
+ *
+ * @hide
+ */
+ public HdmiHotplugEvent(int port, boolean connected) {
+ mPort = port;
+ mConnected = connected;
+ }
+
+ /**
+ * Return the port number for which the event occurred.
+ *
+ * @return port number
+ */
+ public int getPort() {
+ return mPort;
+ }
+
+ /**
+ * Return the connection status associated with this event
+ *
+ * @return true if the device gets connected; otherwise false
+ */
+ public boolean isConnected() {
+ return mConnected;
+ }
+
+ /**
+ * Describe the kinds of special objects contained in this Parcelable's
+ * marshalled representation.
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mPort);
+ dest.writeByte((byte) (mConnected ? 1 : 0));
+ }
+
+ public static final Parcelable.Creator<HdmiHotplugEvent> CREATOR
+ = new Parcelable.Creator<HdmiHotplugEvent>() {
+ /**
+ * Rebuild a {@link HdmiHotplugEvent} previously stored with
+ * {@link Parcelable#writeToParcel(Parcel, int)}.
+ *
+ * @param p {@link HdmiHotplugEvent} object to read the Rating from
+ * @return a new {@link HdmiHotplugEvent} created from the data in the parcel
+ */
+ public HdmiHotplugEvent createFromParcel(Parcel p) {
+ int port = p.readInt();
+ boolean connected = p.readByte() == 1;
+ return new HdmiHotplugEvent(port, connected);
+ }
+ public HdmiHotplugEvent[] newArray(int size) {
+ return new HdmiHotplugEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/hardware/hdmi/IHdmiControlService.aidl b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
index f790ed9..8da38e1 100644
--- a/core/java/android/hardware/hdmi/IHdmiControlService.aidl
+++ b/core/java/android/hardware/hdmi/IHdmiControlService.aidl
@@ -18,6 +18,7 @@ package android.hardware.hdmi;
import android.hardware.hdmi.HdmiCecMessage;
import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.IHdmiHotplugEventListener;
/**
* Binder interface that clients running in the application process
@@ -27,6 +28,9 @@ import android.hardware.hdmi.IHdmiControlCallback;
* @hide
*/
interface IHdmiControlService {
- int oneTouchPlay(IHdmiControlCallback callback);
- int queryDisplayStatus(IHdmiControlCallback callback);
+ int[] getSupportedTypes();
+ void oneTouchPlay(IHdmiControlCallback callback);
+ void queryDisplayStatus(IHdmiControlCallback callback);
+ void addHotplugEventListener(IHdmiHotplugEventListener listener);
+ void removeHotplugEventListener(IHdmiHotplugEventListener listener);
}
diff --git a/core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl b/core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl
new file mode 100644
index 0000000..5d63264
--- /dev/null
+++ b/core/java/android/hardware/hdmi/IHdmiHotplugEventListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.hdmi;
+
+import android.hardware.hdmi.HdmiHotplugEvent;
+
+/**
+ * Callback interface definition for HDMI client to get informed of
+ * the result of various API invocation.
+ *
+ * @hide
+ */
+oneway interface IHdmiHotplugEventListener {
+ void onReceived(in HdmiHotplugEvent event);
+}
diff --git a/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
new file mode 100644
index 0000000..63c2182
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/DevicePowerStatusAction.java
@@ -0,0 +1,105 @@
+package com.android.server.hdmi;
+
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Feature action that queries the power status of other device.
+ *
+ * This action is initiated via {@link HdmiControlManager#queryDisplayStatus()} from
+ * the Android system working as playback device to get the power status of TV device.
+ *
+ * <p>Package-private, accessed by {@link HdmiControlService} only.
+ */
+
+final class DevicePowerStatusAction extends FeatureAction {
+ private static final String TAG = "DevicePowerStatusAction";
+
+ // State in which the action is waiting for <Report Power Status>.
+ private static final int STATE_WAITING_FOR_REPORT_POWER_STATUS = 1;
+
+ private final int mTargetAddress;
+ private final IHdmiControlCallback mCallback;
+
+ static DevicePowerStatusAction create(HdmiControlService service, int sourceAddress,
+ int targetAddress, IHdmiControlCallback callback) {
+ if (service == null || callback == null) {
+ Slog.e(TAG, "Wrong arguments");
+ return null;
+ }
+ return new DevicePowerStatusAction(service, sourceAddress, targetAddress, callback);
+ }
+
+ private DevicePowerStatusAction(HdmiControlService service, int sourceAddress,
+ int targetAddress, IHdmiControlCallback callback) {
+ super(service, sourceAddress);
+ mTargetAddress = targetAddress;
+ mCallback = callback;
+ }
+
+ @Override
+ boolean start() {
+ queryDevicePowerStatus();
+ mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
+ addTimer(mState, FeatureAction.TIMEOUT_MS);
+ return true;
+ }
+
+ private void queryDevicePowerStatus() {
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildGiveDevicePowerStatus(mSourceAddress, mTargetAddress));
+ }
+
+ @Override
+ boolean processCommand(HdmiCecMessage cmd) {
+ if (mState != STATE_WAITING_FOR_REPORT_POWER_STATUS) {
+ return false;
+ }
+ if (cmd.getOpcode() == HdmiCec.MESSAGE_REPORT_POWER_STATUS) {
+ int status = cmd.getParams()[0];
+ invokeCallback(status);
+ finish();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ void handleTimerEvent(int state) {
+ if (mState != state) {
+ return;
+ }
+ if (state == STATE_WAITING_FOR_REPORT_POWER_STATUS) {
+ // Got no response from TV. Report status 'unknown'.
+ invokeCallback(HdmiCec.POWER_STATUS_UNKNOWN);
+ finish();
+ }
+ }
+
+ private void invokeCallback(int result) {
+ try {
+ mCallback.onComplete(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Callback failed:" + e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index e47b90d..be270b9 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -230,6 +230,40 @@ public class HdmiCecMessageBuilder {
}
/**
+ * Build &lt;Text View On&gt; command.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildTextViewOn(int src, int dest) {
+ return buildCommand(src, dest, HdmiCec.MESSAGE_TEXT_VIEW_ON);
+ }
+
+ /**
+ * Build &lt;Active Source&gt; command.
+ *
+ * @param src source address of command
+ * @param physicalAddress physical address of the device to become active
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildActiveSource(int src, int physicalAddress) {
+ return buildCommand(src, HdmiCec.ADDR_BROADCAST, HdmiCec.MESSAGE_ACTIVE_SOURCE,
+ physicalAddressToParam(physicalAddress));
+ }
+
+ /**
+ * Build &lt;Give Device Power Status&gt; command.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildGiveDevicePowerStatus(int src, int dest) {
+ return buildCommand(src, dest, HdmiCec.MESSAGE_GIVE_DEVICE_POWER_STATUS);
+ }
+
+ /**
* Build a {@link HdmiCecMessage} without extra parameter.
*
* @param src source address of command
@@ -253,4 +287,11 @@ public class HdmiCecMessageBuilder {
private static HdmiCecMessage buildCommand(int src, int dest, int opcode, byte[] params) {
return new HdmiCecMessage(src, dest, opcode, params);
}
+
+ private static byte[] physicalAddressToParam(int physicalAddress) {
+ return new byte[] {
+ (byte) (physicalAddress >> 8),
+ (byte) (physicalAddress & 0xFF)
+ };
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 963e818..09153b9 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -18,18 +18,30 @@ package com.android.server.hdmi;
import android.annotation.Nullable;
import android.content.Context;
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.IHdmiControlService;
+import android.hardware.hdmi.IHdmiHotplugEventListener;
import android.hardware.hdmi.HdmiCec;
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiCecMessage;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.IBinder;
import android.os.Looper;
+import android.os.RemoteException;
import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
import com.android.server.SystemService;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
+import java.util.List;
import java.util.Locale;
/**
@@ -39,6 +51,9 @@ import java.util.Locale;
public final class HdmiControlService extends SystemService {
private static final String TAG = "HdmiControlService";
+ // TODO: Rename the permission to HDMI_CONTROL.
+ private static final String PERMISSION = "android.permission.HDMI_CEC";
+
// A thread to handle synchronous IO of CEC and MHL control service.
// Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
// and sparse call it shares a thread to handle IO operations.
@@ -48,6 +63,21 @@ public final class HdmiControlService extends SystemService {
// Note that access to this collection should happen in service thread.
private final LinkedList<FeatureAction> mActions = new LinkedList<>();
+ // Used to synchronize the access to the service.
+ private final Object mLock = new Object();
+
+ // Type of logical devices hosted in the system.
+ @GuardedBy("mLock")
+ private final int[] mLocalDevices;
+
+ // List of listeners registered by callers that want to get notified of
+ // hotplug events.
+ private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
+
+ // List of records for hotplug event listener to handle the the caller killed in action.
+ private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
+ new ArrayList<>();
+
@Nullable
private HdmiCecController mCecController;
@@ -63,6 +93,8 @@ public final class HdmiControlService extends SystemService {
public HdmiControlService(Context context) {
super(context);
+ mLocalDevices = getContext().getResources().getIntArray(
+ com.android.internal.R.array.config_hdmiCecLogicalDeviceType);
}
@Override
@@ -70,8 +102,7 @@ public final class HdmiControlService extends SystemService {
mIoThread.start();
mCecController = HdmiCecController.create(this);
if (mCecController != null) {
- mCecController.initializeLocalDevices(getContext().getResources()
- .getIntArray(com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
+ mCecController.initializeLocalDevices(mLocalDevices);
} else {
Slog.i(TAG, "Device does not support HDMI-CEC.");
}
@@ -80,6 +111,9 @@ public final class HdmiControlService extends SystemService {
if (mMhlController == null) {
Slog.i(TAG, "Device does not support MHL-control.");
}
+
+ // TODO: Publish the BinderService
+ // publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
}
/**
@@ -306,4 +340,95 @@ public final class HdmiControlService extends SystemService {
Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString());
}
}
+
+ // Record class that monitors the event of the caller of being killed. Used to clean up
+ // the listener list and record list accordingly.
+ private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
+ private final IHdmiHotplugEventListener mListener;
+
+ public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mHotplugEventListenerRecords.remove(this);
+ mHotplugEventListeners.remove(mListener);
+ }
+ }
+ }
+
+ private void enforceAccessPermission() {
+ getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
+ }
+
+ private final class BinderService extends IHdmiControlService.Stub {
+ @Override
+ public int[] getSupportedTypes() {
+ enforceAccessPermission();
+ synchronized (mLock) {
+ return mLocalDevices;
+ }
+ }
+
+ @Override
+ public void oneTouchPlay(IHdmiControlCallback callback) {
+ enforceAccessPermission();
+ // TODO: Post a message for HdmiControlService#oneTouchPlay()
+ }
+
+ @Override
+ public void queryDisplayStatus(IHdmiControlCallback callback) {
+ enforceAccessPermission();
+ // TODO: Post a message for HdmiControlService#queryDisplayStatus()
+ }
+
+ @Override
+ public void addHotplugEventListener(IHdmiHotplugEventListener listener) {
+ enforceAccessPermission();
+ // TODO: Post a message for HdmiControlService#addHotplugEventListener()
+ }
+
+ @Override
+ public void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
+ enforceAccessPermission();
+ // TODO: Post a message for HdmiControlService#removeHotplugEventListener()
+ }
+ }
+
+ private void oneTouchPlay(IHdmiControlCallback callback) {
+ // TODO: Create a new action
+ }
+
+ private void queryDisplayStatus(IHdmiControlCallback callback) {
+ // TODO: Create a new action
+ }
+
+ private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
+ HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
+ try {
+ listener.asBinder().linkToDeath(record, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Listener already died");
+ return;
+ }
+ synchronized (mLock) {
+ mHotplugEventListenerRecords.add(record);
+ mHotplugEventListeners.add(listener);
+ }
+ }
+
+ private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
+ synchronized (mLock) {
+ for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
+ if (record.mListener.asBinder() == listener.asBinder()) {
+ listener.asBinder().unlinkToDeath(record, 0);
+ mHotplugEventListenerRecords.remove(record);
+ break;
+ }
+ }
+ mHotplugEventListeners.remove(listener);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
new file mode 100644
index 0000000..69fad13
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/OneTouchPlayAction.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.IHdmiControlCallback;
+import android.hardware.hdmi.HdmiCec;
+import android.hardware.hdmi.HdmiCecMessage;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Feature action that performs one touch play against TV/Display device.
+ *
+ * This action is initiated via {@link HdmiControlManager#oneTouchPlay()} from
+ * the Android system working as playback device to turn on the TV, and switch the input.
+ *
+ * <p>Package-private, accessed by {@link HdmiControlService} only.
+ */
+
+public final class OneTouchPlayAction extends FeatureAction {
+ private static final String TAG = "OneTouchPlayAction";
+
+ // State in which the action is waiting for <Report Power Status>. In normal situation
+ // source device can simply send <Text|Image View On> and <Active Source> in succession
+ // since the standard requires that the TV/Display should buffer the <Active Source>
+ // if the TV is brought of out standby state.
+ //
+ // But there are TV's that fail to buffer the <Active Source> while getting out of
+ // standby mode, and do not accept the command until their power status becomes 'ON'.
+ // For a workaround, we send <Give Device Power Status> commands periodically to make sure
+ // the device switches its status to 'ON'. Then we send additional <Active Source>.
+ private static final int STATE_WAITING_FOR_REPORT_POWER_STATUS = 1;
+
+ // The maximum number of times we send <Give Device Power Status> before we give up.
+ // We wait up to RESPONSE_TIMEOUT_MS * LOOP_COUNTER_MAX = 20 seconds.
+ private static final int LOOP_COUNTER_MAX = 10;
+
+ private final int mSourcePath;
+ private final int mTargetAddress;
+ private final IHdmiControlCallback mCallback;
+
+ private int mPowerStatusCounter = 0;
+
+ // Factory method. Ensures arguments are valid.
+ static OneTouchPlayAction create(HdmiControlService service, int sourceAddress,
+ int sourcePath, int targetAddress, IHdmiControlCallback callback) {
+ if (service == null || callback == null) {
+ Slog.e(TAG, "Wrong arguments");
+ return null;
+ }
+ return new OneTouchPlayAction(service, sourceAddress, sourcePath, targetAddress, callback);
+ }
+
+ private OneTouchPlayAction(HdmiControlService service, int sourceAddress, int sourcePath,
+ int targetAddress, IHdmiControlCallback callback) {
+ super(service, sourceAddress);
+ mSourcePath = sourcePath;
+ mTargetAddress = targetAddress;
+ mCallback = callback;
+ }
+
+ @Override
+ boolean start() {
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildTextViewOn(mSourceAddress, mTargetAddress));
+ broadcastActiveSource();
+ queryDevicePowerStatus();
+ mState = STATE_WAITING_FOR_REPORT_POWER_STATUS;
+ addTimer(mState, FeatureAction.TIMEOUT_MS);
+ return true;
+ }
+
+ private void broadcastActiveSource() {
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildActiveSource(mSourceAddress, mSourcePath));
+ }
+
+ private void queryDevicePowerStatus() {
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildGiveDevicePowerStatus(mSourceAddress, mTargetAddress));
+ }
+
+ @Override
+ boolean processCommand(HdmiCecMessage cmd) {
+ if (mState != STATE_WAITING_FOR_REPORT_POWER_STATUS) {
+ return false;
+ }
+ if (cmd.getOpcode() == HdmiCec.MESSAGE_REPORT_POWER_STATUS) {
+ int status = cmd.getParams()[0];
+ if (status == HdmiCec.POWER_STATUS_ON) {
+ broadcastActiveSource();
+ invokeCallback(HdmiCec.RESULT_SUCCESS);
+ finish();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ void handleTimerEvent(int state) {
+ if (mState != state) {
+ return;
+ }
+ if (state == STATE_WAITING_FOR_REPORT_POWER_STATUS) {
+ if (mPowerStatusCounter++ < LOOP_COUNTER_MAX) {
+ queryDevicePowerStatus();
+ addTimer(mState, FeatureAction.TIMEOUT_MS);
+ } else {
+ // Couldn't wake up the TV for whatever reason. Report failure.
+ invokeCallback(HdmiCec.RESULT_TIMEOUT);
+ finish();
+ }
+ }
+ }
+
+ private void invokeCallback(int result) {
+ try {
+ mCallback.onComplete(result);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Callback failed:" + e);
+ }
+ }
+}