summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorJinsuk Kim <jinsukkim@google.com>2014-05-13 16:36:15 +0900
committerJinsuk Kim <jinsukkim@google.com>2014-05-22 11:15:51 +0900
commit78d695d8ba532214b02e7f18e0ccf89cf099163d (patch)
treec9d0b207e995dad311af7d270a881358ddda6891 /services
parente1a2e26a03e76279c9b03bdd81f92df70f8707e7 (diff)
downloadframeworks_base-78d695d8ba532214b02e7f18e0ccf89cf099163d.zip
frameworks_base-78d695d8ba532214b02e7f18e0ccf89cf099163d.tar.gz
frameworks_base-78d695d8ba532214b02e7f18e0ccf89cf099163d.tar.bz2
Add feature actions for HDMI-CEC playback device
- OneTouchPlayAction - DevicePowerStatusAction - addHotplugEventListener - removeHotplugEventListener Change-Id: Ia7f31507ca62127efbacbbfe07ab43ba1f9bd4cf
Diffstat (limited to 'services')
-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
4 files changed, 410 insertions, 2 deletions
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);
+ }
+ }
+}