summaryrefslogtreecommitdiffstats
path: root/media/java
diff options
context:
space:
mode:
Diffstat (limited to 'media/java')
-rw-r--r--media/java/android/media/AudioFormat.java152
-rw-r--r--media/java/android/media/AudioManager.java12
-rw-r--r--media/java/android/media/AudioPort.java2
-rw-r--r--media/java/android/media/AudioTrack.java2
-rw-r--r--media/java/android/media/tv/ITvInputClient.aidl37
-rw-r--r--media/java/android/media/tv/ITvInputHardware.aidl46
-rw-r--r--media/java/android/media/tv/ITvInputHardwareCallback.aidl27
-rw-r--r--media/java/android/media/tv/ITvInputManager.aidl58
-rw-r--r--media/java/android/media/tv/ITvInputService.aidl31
-rw-r--r--media/java/android/media/tv/ITvInputServiceCallback.aidl28
-rw-r--r--media/java/android/media/tv/ITvInputSession.aidl39
-rw-r--r--media/java/android/media/tv/ITvInputSessionCallback.aidl33
-rw-r--r--media/java/android/media/tv/ITvInputSessionWrapper.java177
-rw-r--r--media/java/android/media/tv/TvContract.java805
-rw-r--r--media/java/android/media/tv/TvInputHardwareInfo.aidl20
-rw-r--r--media/java/android/media/tv/TvInputHardwareInfo.java93
-rw-r--r--media/java/android/media/tv/TvInputInfo.aidl19
-rw-r--r--media/java/android/media/tv/TvInputInfo.java280
-rw-r--r--media/java/android/media/tv/TvInputManager.java917
-rw-r--r--media/java/android/media/tv/TvInputService.java651
-rw-r--r--media/java/android/media/tv/TvStreamConfig.aidl20
-rw-r--r--media/java/android/media/tv/TvStreamConfig.java157
-rw-r--r--media/java/android/media/tv/TvView.java423
23 files changed, 4020 insertions, 9 deletions
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index b07d2c5..4b4be1b 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -16,6 +16,11 @@
package android.media;
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* The AudioFormat class is used to access a number of audio format and
* channel configuration constants. They are for instance used
@@ -162,4 +167,151 @@ public class AudioFormat {
throw new UnsupportedOperationException("There is no valid usage of this constructor");
}
+ /**
+ * Private constructor with an ignored argument to differentiate from the removed default ctor
+ * @param ignoredArgument
+ */
+ private AudioFormat(int ignoredArgument) {
+ }
+
+ /** @hide */
+ public final static int AUDIO_FORMAT_HAS_PROPERTY_NONE = 0x0;
+ /** @hide */
+ public final static int AUDIO_FORMAT_HAS_PROPERTY_ENCODING = 0x1 << 0;
+ /** @hide */
+ public final static int AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE = 0x1 << 1;
+ /** @hide */
+ public final static int AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK = 0x1 << 2;
+
+ private int mEncoding;
+ private int mSampleRate;
+ private int mChannelMask;
+ private int mPropertySetMask;
+
+ /**
+ * @hide CANDIDATE FOR PUBLIC API
+ * Builder class for {@link AudioFormat} objects.
+ */
+ public static class Builder {
+ private int mEncoding = ENCODING_DEFAULT;
+ private int mSampleRate = 0;
+ private int mChannelMask = CHANNEL_INVALID;
+ private int mPropertySetMask = AUDIO_FORMAT_HAS_PROPERTY_NONE;
+
+ /**
+ * Constructs a new Builder with the defaults.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Constructs a new Builder from a given {@link AudioFormat}.
+ * @param af the {@link AudioFormat} object whose data will be reused in the new Builder.
+ */
+ public Builder(AudioFormat af) {
+ mEncoding = af.mEncoding;
+ mSampleRate = af.mSampleRate;
+ mChannelMask = af.mChannelMask;
+ mPropertySetMask = af.mPropertySetMask;
+ }
+
+ /**
+ * Combines all of the format characteristics that have been set and return a new
+ * {@link AudioFormat} object.
+ * @return a new {@link AudioFormat} object
+ */
+ public AudioFormat build() {
+ AudioFormat af = new AudioFormat(1980/*ignored*/);
+ af.mEncoding = mEncoding;
+ af.mSampleRate = mSampleRate;
+ af.mChannelMask = mChannelMask;
+ af.mPropertySetMask = mPropertySetMask;
+ return af;
+ }
+
+ /**
+ * Sets the data encoding format.
+ * @param encoding one of {@link AudioFormat#ENCODING_DEFAULT},
+ * {@link AudioFormat#ENCODING_PCM_8BIT},
+ * {@link AudioFormat#ENCODING_PCM_16BIT},
+ * {@link AudioFormat#ENCODING_PCM_FLOAT}.
+ * @return the same Builder instance.
+ * @throws java.lang.IllegalArgumentException
+ */
+ public Builder setEncoding(@Encoding int encoding) throws IllegalArgumentException {
+ switch (encoding) {
+ case ENCODING_DEFAULT:
+ mEncoding = ENCODING_PCM_16BIT;
+ break;
+ case ENCODING_PCM_8BIT:
+ case ENCODING_PCM_16BIT:
+ case ENCODING_PCM_FLOAT:
+ mEncoding = encoding;
+ break;
+ case ENCODING_INVALID:
+ default:
+ throw new IllegalArgumentException("Invalid encoding " + encoding);
+ }
+ mPropertySetMask |= AUDIO_FORMAT_HAS_PROPERTY_ENCODING;
+ return this;
+ }
+
+ /**
+ * Sets the channel mask.
+ * @param channelMask describes the configuration of the audio channels.
+ * <p>For output, the mask should be a combination of
+ * {@link AudioFormat#CHANNEL_OUT_FRONT_LEFT},
+ * {@link AudioFormat#CHANNEL_OUT_FRONT_CENTER},
+ * {@link AudioFormat#CHANNEL_OUT_FRONT_RIGHT},
+ * {@link AudioFormat#CHANNEL_OUT_SIDE_LEFT},
+ * {@link AudioFormat#CHANNEL_OUT_SIDE_RIGHT},
+ * {@link AudioFormat#CHANNEL_OUT_BACK_LEFT},
+ * {@link AudioFormat#CHANNEL_OUT_BACK_RIGHT}.
+ * <p>for input, the mask should be {@link AudioFormat#CHANNEL_IN_MONO} or
+ * {@link AudioFormat#CHANNEL_IN_STEREO}. {@link AudioFormat#CHANNEL_IN_MONO} is
+ * guaranteed to work on all devices.
+ * @return the same Builder instance.
+ */
+ public Builder setChannelMask(int channelMask) {
+ // only validated when used, with input or output context
+ mChannelMask = channelMask;
+ mPropertySetMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK;
+ return this;
+ }
+
+ /**
+ * Sets the sample rate.
+ * @param sampleRate the sample rate expressed in Hz
+ * @return the same Builder instance.
+ * @throws java.lang.IllegalArgumentException
+ */
+ public Builder setSampleRate(int sampleRate) throws IllegalArgumentException {
+ if ((sampleRate <= 0) || (sampleRate > 192000)) {
+ throw new IllegalArgumentException("Invalid sample rate " + sampleRate);
+ }
+ mSampleRate = sampleRate;
+ mPropertySetMask |= AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE;
+ return this;
+ }
+ }
+
+ @Override
+ public String toString () {
+ return new String("AudioFormat:"
+ + " props=" + mPropertySetMask
+ + " enc=" + mEncoding
+ + " chan=0x" + Integer.toHexString(mChannelMask)
+ + " rate=" + mSampleRate);
+ }
+
+ /** @hide */
+ @IntDef({
+ ENCODING_DEFAULT,
+ ENCODING_PCM_8BIT,
+ ENCODING_PCM_16BIT,
+ ENCODING_PCM_FLOAT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Encoding {}
+
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 84d4ab6..88756d7 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -3188,15 +3188,11 @@ public class AudioManager {
do {
newPorts.clear();
status = AudioSystem.listAudioPorts(newPorts, portGeneration);
- Log.i(TAG, "updateAudioPortCache AudioSystem.listAudioPorts() status: "+
- status+" num ports: "+ newPorts.size() +" portGeneration: "+portGeneration[0]);
if (status != SUCCESS) {
return status;
}
newPatches.clear();
status = AudioSystem.listAudioPatches(newPatches, patchGeneration);
- Log.i(TAG, "updateAudioPortCache AudioSystem.listAudioPatches() status: "+
- status+" num patches: "+ newPatches.size() +" patchGeneration: "+patchGeneration[0]);
if (status != SUCCESS) {
return status;
}
@@ -3204,14 +3200,16 @@ public class AudioManager {
for (int i = 0; i < newPatches.size(); i++) {
for (int j = 0; j < newPatches.get(i).sources().length; j++) {
- AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sources()[j], newPorts);
+ AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sources()[j],
+ newPorts);
if (portCfg == null) {
return ERROR;
}
newPatches.get(i).sources()[j] = portCfg;
}
for (int j = 0; j < newPatches.get(i).sinks().length; j++) {
- AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sinks()[j], newPorts);
+ AudioPortConfig portCfg = updatePortConfig(newPatches.get(i).sinks()[j],
+ newPorts);
if (portCfg == null) {
return ERROR;
}
@@ -3242,8 +3240,6 @@ public class AudioManager {
// compare handles because the port returned by JNI is not of the correct
// subclass
if (ports.get(k).handle().equals(port.handle())) {
- Log.i(TAG, "updatePortConfig match found for port handle: "+
- port.handle().id()+" port: "+ k);
port = ports.get(k);
break;
}
diff --git a/media/java/android/media/AudioPort.java b/media/java/android/media/AudioPort.java
index fbd5022..8b74842 100644
--- a/media/java/android/media/AudioPort.java
+++ b/media/java/android/media/AudioPort.java
@@ -133,7 +133,7 @@ public class AudioPort {
* Get the gain descriptor at a given index
*/
AudioGain gain(int index) {
- if (index < mGains.length) {
+ if (index < 0 || index >= mGains.length) {
return null;
}
return mGains[index];
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 8eb83e4..cfd9c3b 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -816,6 +816,8 @@ public class AudioTrack
* with the estimated time when that frame was presented or is committed to
* be presented.
* In the case that no timestamp is available, any supplied instance is left unaltered.
+ * A timestamp may be temporarily unavailable while the audio clock is stabilizing,
+ * or during and immediately after a route change.
*/
// Add this text when the "on new timestamp" API is added:
// Use if you need to get the most recent timestamp outside of the event callback handler.
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
new file mode 100644
index 0000000..011da35
--- /dev/null
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.media.tv;
+
+import android.content.ComponentName;
+import android.media.tv.ITvInputSession;
+import android.os.Bundle;
+import android.view.InputChannel;
+
+/**
+ * Interface a client of the ITvInputManager implements, to identify itself and receive information
+ * about changes to the state of each TV input service.
+ * @hide
+ */
+oneway interface ITvInputClient {
+ void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq);
+ void onAvailabilityChanged(in String inputId, boolean isAvailable);
+ void onSessionReleased(int seq);
+ void onSessionEvent(in String name, in Bundle args, int seq);
+ void onVideoStreamChanged(int width, int height, boolean interlaced, int seq);
+ void onAudioStreamChanged(int channelCount, int seq);
+ void onClosedCaptionStreamChanged(boolean hasClosedCaption, int seq);
+}
diff --git a/media/java/android/media/tv/ITvInputHardware.aidl b/media/java/android/media/tv/ITvInputHardware.aidl
new file mode 100644
index 0000000..f35e8f3
--- /dev/null
+++ b/media/java/android/media/tv/ITvInputHardware.aidl
@@ -0,0 +1,46 @@
+/*
+ * 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.media.tv;
+
+import android.media.tv.TvStreamConfig;
+import android.view.KeyEvent;
+import android.view.Surface;
+
+/**
+ * TvInputService representing a physical port should connect to HAL through this interface.
+ * Framework will take care of communication among system services including TvInputManagerService,
+ * HdmiControlService, AudioService, etc.
+ *
+ * @hide
+ */
+interface ITvInputHardware {
+ /**
+ * Make the input render on the surface according to the config. In case of HDMI, this will
+ * trigger CEC commands for adjusting active HDMI source. Returns true on success.
+ */
+ boolean setSurface(in Surface surface, in TvStreamConfig config);
+ /**
+ * Set volume for this stream via AudioGain. (TBD)
+ */
+ void setVolume(float volume);
+
+ /**
+ * Dispatch key event to HDMI service. The events would be automatically converted to
+ * HDMI CEC commands. If the hardware is not representing an HDMI port, this method will fail.
+ */
+ boolean dispatchKeyEventToHdmi(in KeyEvent event);
+}
diff --git a/media/java/android/media/tv/ITvInputHardwareCallback.aidl b/media/java/android/media/tv/ITvInputHardwareCallback.aidl
new file mode 100644
index 0000000..870883b
--- /dev/null
+++ b/media/java/android/media/tv/ITvInputHardwareCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.media.tv;
+
+import android.media.tv.TvStreamConfig;
+
+/**
+ * @hide
+ */
+oneway interface ITvInputHardwareCallback {
+ void onReleased();
+ void onStreamConfigChanged(in TvStreamConfig[] configs);
+}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
new file mode 100644
index 0000000..6db5a18
--- /dev/null
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -0,0 +1,58 @@
+/*
+ * 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.media.tv;
+
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.media.tv.ITvInputHardware;
+import android.media.tv.ITvInputHardwareCallback;
+import android.media.tv.ITvInputClient;
+import android.media.tv.TvInputHardwareInfo;
+import android.media.tv.TvInputInfo;
+import android.net.Uri;
+import android.view.Surface;
+
+/**
+ * Interface to the TV input manager service.
+ * @hide
+ */
+interface ITvInputManager {
+ List<TvInputInfo> getTvInputList(int userId);
+
+ boolean getAvailability(in ITvInputClient client, in String inputId, int userId);
+
+ void registerCallback(in ITvInputClient client, in String inputId, int userId);
+ void unregisterCallback(in ITvInputClient client, in String inputId, int userId);
+
+ void createSession(in ITvInputClient client, in String inputId, int seq, int userId);
+ void releaseSession(in IBinder sessionToken, int userId);
+
+ void setSurface(in IBinder sessionToken, in Surface surface, int userId);
+ void setVolume(in IBinder sessionToken, float volume, int userId);
+ void tune(in IBinder sessionToken, in Uri channelUri, int userId);
+
+ void createOverlayView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
+ int userId);
+ void relayoutOverlayView(in IBinder sessionToken, in Rect frame, int userId);
+ void removeOverlayView(in IBinder sessionToken, int userId);
+
+ // For TV input hardware binding
+ List<TvInputHardwareInfo> getHardwareList();
+ ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
+ int userId);
+ void releaseTvInputHardware(int deviceId, in ITvInputHardware hardware, int userId);
+}
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
new file mode 100644
index 0000000..992e424
--- /dev/null
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.media.tv;
+
+import android.media.tv.ITvInputServiceCallback;
+import android.media.tv.ITvInputSessionCallback;
+import android.view.InputChannel;
+
+/**
+ * Top-level interface to a TV input component (implemented in a Service).
+ * @hide
+ */
+oneway interface ITvInputService {
+ void registerCallback(ITvInputServiceCallback callback);
+ void unregisterCallback(in ITvInputServiceCallback callback);
+ void createSession(in InputChannel channel, ITvInputSessionCallback callback);
+}
diff --git a/media/java/android/media/tv/ITvInputServiceCallback.aidl b/media/java/android/media/tv/ITvInputServiceCallback.aidl
new file mode 100644
index 0000000..c9484dd
--- /dev/null
+++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.media.tv;
+
+import android.content.ComponentName;
+
+/**
+ * Helper interface for ITvInputService to allow the TV input to notify the client when its status
+ * has been changed.
+ * @hide
+ */
+oneway interface ITvInputServiceCallback {
+ void onAvailabilityChanged(in String inputId, boolean isAvailable);
+}
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
new file mode 100644
index 0000000..fb2e251
--- /dev/null
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.media.tv;
+
+import android.graphics.Rect;
+import android.net.Uri;
+import android.view.Surface;
+
+/**
+ * Sub-interface of ITvInputService which is created per session and has its own context.
+ * @hide
+ */
+oneway interface ITvInputSession {
+ void release();
+
+ void setSurface(in Surface surface);
+ // TODO: Remove this once it becomes irrelevant for applications to handle audio focus. The plan
+ // is to introduce some new concepts that will solve a number of problems in audio policy today.
+ void setVolume(float volume);
+ void tune(in Uri channelUri);
+
+ void createOverlayView(in IBinder windowToken, in Rect frame);
+ void relayoutOverlayView(in Rect frame);
+ void removeOverlayView();
+}
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
new file mode 100644
index 0000000..00f2922
--- /dev/null
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.media.tv;
+
+import android.media.tv.ITvInputSession;
+import android.os.Bundle;
+
+/**
+ * Helper interface for ITvInputSession to allow the TV input to notify the system service when a
+ * new session has been created.
+ * @hide
+ */
+oneway interface ITvInputSessionCallback {
+ void onSessionCreated(ITvInputSession session);
+ void onSessionEvent(in String name, in Bundle args);
+ void onVideoStreamChanged(int width, int height, boolean interlaced);
+ void onAudioStreamChanged(int channelCount);
+ void onClosedCaptionStreamChanged(boolean hasClosedCaption);
+}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
new file mode 100644
index 0000000..975e391
--- /dev/null
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -0,0 +1,177 @@
+/*
+ * 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.media.tv;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.media.tv.TvInputManager.Session;
+import android.media.tv.TvInputService.TvInputSessionImpl;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.Surface;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+/**
+ * Implements the internal ITvInputSession interface to convert incoming calls on to it back to
+ * calls on the public TvInputSession interface, scheduling them on the main thread of the process.
+ *
+ * @hide
+ */
+public class ITvInputSessionWrapper extends ITvInputSession.Stub implements HandlerCaller.Callback {
+ private static final String TAG = "TvInputSessionWrapper";
+
+ private static final int DO_RELEASE = 1;
+ private static final int DO_SET_SURFACE = 2;
+ private static final int DO_SET_VOLUME = 3;
+ private static final int DO_TUNE = 4;
+ private static final int DO_CREATE_OVERLAY_VIEW = 5;
+ private static final int DO_RELAYOUT_OVERLAY_VIEW = 6;
+ private static final int DO_REMOVE_OVERLAY_VIEW = 7;
+
+ private final HandlerCaller mCaller;
+
+ private TvInputSessionImpl mTvInputSessionImpl;
+ private InputChannel mChannel;
+ private TvInputEventReceiver mReceiver;
+
+ public ITvInputSessionWrapper(Context context, TvInputSessionImpl sessionImpl,
+ InputChannel channel) {
+ mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
+ mTvInputSessionImpl = sessionImpl;
+ mChannel = channel;
+ if (channel != null) {
+ mReceiver = new TvInputEventReceiver(channel, context.getMainLooper());
+ }
+ }
+
+ @Override
+ public void executeMessage(Message msg) {
+ if (mTvInputSessionImpl == null) {
+ return;
+ }
+
+ switch (msg.what) {
+ case DO_RELEASE: {
+ mTvInputSessionImpl.release();
+ mTvInputSessionImpl = null;
+ if (mReceiver != null) {
+ mReceiver.dispose();
+ mReceiver = null;
+ }
+ if (mChannel != null) {
+ mChannel.dispose();
+ mChannel = null;
+ }
+ return;
+ }
+ case DO_SET_SURFACE: {
+ mTvInputSessionImpl.setSurface((Surface) msg.obj);
+ return;
+ }
+ case DO_SET_VOLUME: {
+ mTvInputSessionImpl.setVolume((Float) msg.obj);
+ return;
+ }
+ case DO_TUNE: {
+ mTvInputSessionImpl.tune((Uri) msg.obj);
+ return;
+ }
+ case DO_CREATE_OVERLAY_VIEW: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mTvInputSessionImpl.createOverlayView((IBinder) args.arg1, (Rect) args.arg2);
+ args.recycle();
+ return;
+ }
+ case DO_RELAYOUT_OVERLAY_VIEW: {
+ mTvInputSessionImpl.relayoutOverlayView((Rect) msg.obj);
+ return;
+ }
+ case DO_REMOVE_OVERLAY_VIEW: {
+ mTvInputSessionImpl.removeOverlayView(true);
+ return;
+ }
+ default: {
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void release() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
+ }
+
+ @Override
+ public void setSurface(Surface surface) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
+ }
+
+ @Override
+ public final void setVolume(float volume) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VOLUME, volume));
+ }
+
+ @Override
+ public void tune(Uri channelUri) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TUNE, channelUri));
+ }
+
+ @Override
+ public void createOverlayView(IBinder windowToken, Rect frame) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken,
+ frame));
+ }
+
+ @Override
+ public void relayoutOverlayView(Rect frame) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_OVERLAY_VIEW, frame));
+ }
+
+ @Override
+ public void removeOverlayView() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW));
+ }
+
+ private final class TvInputEventReceiver extends InputEventReceiver {
+ public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ if (mTvInputSessionImpl == null) {
+ // The session has been finished.
+ finishInputEvent(event, false);
+ return;
+ }
+
+ int handled = mTvInputSessionImpl.dispatchInputEvent(event, this);
+ if (handled != Session.DISPATCH_IN_PROGRESS) {
+ finishInputEvent(event, handled == Session.DISPATCH_HANDLED);
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
new file mode 100644
index 0000000..6e0586e
--- /dev/null
+++ b/media/java/android/media/tv/TvContract.java
@@ -0,0 +1,805 @@
+/*
+ * 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.media.tv;
+
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.net.Uri;
+import android.provider.BaseColumns;
+
+import java.util.List;
+
+/**
+ * <p>
+ * The contract between the TV provider and applications. Contains definitions for the supported
+ * URIs and columns.
+ * </p>
+ * <h3>Overview</h3>
+ * <p>
+ * TvContract defines a basic database of TV content metadata such as channel and program
+ * information. The information is stored in {@link Channels} and {@link Programs} tables.
+ * </p>
+ * <ul>
+ * <li>A row in the {@link Channels} table represents information about a TV channel. The data
+ * format can vary greatly from standard to standard or according to service provider, thus
+ * the columns here are mostly comprised of basic entities that are usually seen to users
+ * regardless of standard such as channel number and name.</li>
+ * <li>A row in the {@link Programs} table represents a set of data describing a TV program such
+ * as program title and start time.</li>
+ * </ul>
+ */
+public final class TvContract {
+ /** The authority for the TV provider. */
+ public static final String AUTHORITY = "android.media.tv";
+
+ private static final String PATH_CHANNEL = "channel";
+ private static final String PATH_PROGRAM = "program";
+ private static final String PATH_INPUT = "input";
+
+ /**
+ * An optional query, update or delete URI parameter that allows the caller to specify start
+ * time (in milliseconds since the epoch) to filter programs.
+ *
+ * @hide
+ */
+ public static final String PARAM_START_TIME = "start_time";
+
+ /**
+ * An optional query, update or delete URI parameter that allows the caller to specify end time
+ * (in milliseconds since the epoch) to filter programs.
+ *
+ * @hide
+ */
+ public static final String PARAM_END_TIME = "end_time";
+
+ /**
+ * A query, update or delete URI parameter that allows the caller to operate on all or
+ * browsable-only channels. If set to "true", the rows that contain non-browsable channels are
+ * not affected.
+ *
+ * @hide
+ */
+ public static final String PARAM_BROWSABLE_ONLY = "browsable_only";
+
+ /**
+ * Builds a URI that points to a specific channel.
+ *
+ * @param channelId The ID of the channel to point to.
+ */
+ public static final Uri buildChannelUri(long channelId) {
+ return ContentUris.withAppendedId(Channels.CONTENT_URI, channelId);
+ }
+
+ /**
+ * Builds a URI that points to all browsable channels from a given TV input.
+ *
+ * @param name {@link ComponentName} of the {@link android.media.tv.TvInputService} that
+ * implements the given TV input.
+ */
+ public static final Uri buildChannelsUriForInput(ComponentName name) {
+ return buildChannelsUriForInput(name, true);
+ }
+
+ /**
+ * Builds a URI that points to all or browsable-only channels from a given TV input.
+ *
+ * @param name {@link ComponentName} of the {@link android.media.tv.TvInputService} that
+ * implements the given TV input.
+ * @param browsableOnly If set to {@code true} the URI points to only browsable channels. If set
+ * to {@code false} the URI points to all channels regardless of whether they are
+ * browsable or not.
+ */
+ public static final Uri buildChannelsUriForInput(ComponentName name, boolean browsableOnly) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
+ .appendPath(PATH_INPUT).appendPath(name.getPackageName())
+ .appendPath(name.getClassName()).appendPath(PATH_CHANNEL)
+ .appendQueryParameter(PARAM_BROWSABLE_ONLY, String.valueOf(browsableOnly)).build();
+ }
+
+ /**
+ * Builds a URI that points to a specific program.
+ *
+ * @param programId The ID of the program to point to.
+ */
+ public static final Uri buildProgramUri(long programId) {
+ return ContentUris.withAppendedId(Programs.CONTENT_URI, programId);
+ }
+
+ /**
+ * Builds a URI that points to all programs on a given channel.
+ *
+ * @param channelUri The URI of the channel to return programs for.
+ */
+ public static final Uri buildProgramsUriForChannel(Uri channelUri) {
+ if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
+ throw new IllegalArgumentException("Not a channel: " + channelUri);
+ }
+ String channelId = String.valueOf(ContentUris.parseId(channelUri));
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
+ .appendPath(PATH_CHANNEL).appendPath(channelId).appendPath(PATH_PROGRAM).build();
+ }
+
+ /**
+ * Builds a URI that points to programs on a specific channel whose schedules overlap with the
+ * given time frame.
+ *
+ * @param channelUri The URI of the channel to return programs for.
+ * @param startTime The start time used to filter programs. The returned programs should have
+ * {@link Programs#COLUMN_END_TIME_UTC_MILLIS} that is greater than this time.
+ * @param endTime The end time used to filter programs. The returned programs should have
+ * {@link Programs#COLUMN_START_TIME_UTC_MILLIS} that is less than this time.
+ */
+ public static final Uri buildProgramsUriForChannel(Uri channelUri, long startTime,
+ long endTime) {
+ Uri uri = buildProgramsUriForChannel(channelUri);
+ return uri.buildUpon().appendQueryParameter(PARAM_START_TIME, String.valueOf(startTime))
+ .appendQueryParameter(PARAM_END_TIME, String.valueOf(endTime)).build();
+ }
+
+ /**
+ * Builds a URI that points to a specific program the user watched.
+ *
+ * @param watchedProgramId The ID of the watched program to point to.
+ * @hide
+ */
+ public static final Uri buildWatchedProgramUri(long watchedProgramId) {
+ return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, watchedProgramId);
+ }
+
+ /**
+ * Extracts the {@link Channels#COLUMN_PACKAGE_NAME} from a given URI.
+ *
+ * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or
+ * {@link #buildChannelsUriForInput(ComponentName, boolean)}.
+ * @hide
+ */
+ public static final String getPackageName(Uri channelsUri) {
+ final List<String> paths = channelsUri.getPathSegments();
+ if (paths.size() < 4) {
+ throw new IllegalArgumentException("Not channels: " + channelsUri);
+ }
+ if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) {
+ throw new IllegalArgumentException("Not channels: " + channelsUri);
+ }
+ return paths.get(1);
+ }
+
+ /**
+ * Extracts the {@link Channels#COLUMN_SERVICE_NAME} from a given URI.
+ *
+ * @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or
+ * {@link #buildChannelsUriForInput(ComponentName, boolean)}.
+ * @hide
+ */
+ public static final String getServiceName(Uri channelsUri) {
+ final List<String> paths = channelsUri.getPathSegments();
+ if (paths.size() < 4) {
+ throw new IllegalArgumentException("Not channels: " + channelsUri);
+ }
+ if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) {
+ throw new IllegalArgumentException("Not channels: " + channelsUri);
+ }
+ return paths.get(2);
+ }
+
+ /**
+ * Extracts the {@link Channels#_ID} from a given URI.
+ *
+ * @param programsUri A URI constructed by {@link #buildProgramsUriForChannel(Uri)} or
+ * {@link #buildProgramsUriForChannel(Uri, long, long)}.
+ * @hide
+ */
+ public static final String getChannelId(Uri programsUri) {
+ final List<String> paths = programsUri.getPathSegments();
+ if (paths.size() < 3) {
+ throw new IllegalArgumentException("Not programs: " + programsUri);
+ }
+ if (!PATH_CHANNEL.equals(paths.get(0)) || !PATH_PROGRAM.equals(paths.get(2))) {
+ throw new IllegalArgumentException("Not programs: " + programsUri);
+ }
+ return paths.get(1);
+ }
+
+
+ private TvContract() {}
+
+ /**
+ * Common base for the tables of TV channels/programs.
+ */
+ public interface BaseTvColumns extends BaseColumns {
+ /**
+ * The name of the package that owns a row in each table.
+ * <p>
+ * The TV provider fills it in with the name of the package that provides the initial data
+ * of that row. If the package is later uninstalled, the rows it owns are automatically
+ * removed from the tables.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_PACKAGE_NAME = "package_name";
+ }
+
+ /** Column definitions for the TV channels table. */
+ public static final class Channels implements BaseTvColumns {
+
+ /** The content:// style URI for this table. */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"
+ + PATH_CHANNEL);
+
+ /** The MIME type of a directory of TV channels. */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/channel";
+
+ /** The MIME type of a single TV channel. */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/channel";
+
+ /** A generic channel type. */
+ public static final int TYPE_OTHER = 0x0;
+
+ /** The special channel type used for pass-through inputs such as HDMI. */
+ public static final int TYPE_PASSTHROUGH = 0x00010000;
+
+ /** The channel type for DVB-T (terrestrial). */
+ public static final int TYPE_DVB_T = 0x00020000;
+
+ /** The channel type for DVB-T2 (terrestrial). */
+ public static final int TYPE_DVB_T2 = 0x00020001;
+
+ /** The channel type for DVB-S (satellite). */
+ public static final int TYPE_DVB_S = 0x00020100;
+
+ /** The channel type for DVB-S2 (satellite). */
+ public static final int TYPE_DVB_S2 = 0x00020101;
+
+ /** The channel type for DVB-C (cable). */
+ public static final int TYPE_DVB_C = 0x00020200;
+
+ /** The channel type for DVB-C2 (cable). */
+ public static final int TYPE_DVB_C2 = 0x00020201;
+
+ /** The channel type for DVB-H (handheld). */
+ public static final int TYPE_DVB_H = 0x00020300;
+
+ /** The channel type for DVB-SH (satellite). */
+ public static final int TYPE_DVB_SH = 0x00020400;
+
+ /** The channel type for ATSC (terrestrial). */
+ public static final int TYPE_ATSC_T = 0x00030000;
+
+ /** The channel type for ATSC (cable). */
+ public static final int TYPE_ATSC_C = 0x00030200;
+
+ /** The channel type for ATSC-M/H (mobile/handheld). */
+ public static final int TYPE_ATSC_M_H = 0x00030200;
+
+ /** The channel type for ISDB-T (terrestrial). */
+ public static final int TYPE_ISDB_T = 0x00040000;
+
+ /** The channel type for ISDB-Tb (Brazil). */
+ public static final int TYPE_ISDB_TB = 0x00040100;
+
+ /** The channel type for ISDB-S (satellite). */
+ public static final int TYPE_ISDB_S = 0x00040200;
+
+ /** The channel type for ISDB-C (cable). */
+ public static final int TYPE_ISDB_C = 0x00040300;
+
+ /** The channel type for 1seg (handheld). */
+ public static final int TYPE_1SEG = 0x00040400;
+
+ /** The channel type for DTMB (terrestrial). */
+ public static final int TYPE_DTMB = 0x00050000;
+
+ /** The channel type for CMMB (handheld). */
+ public static final int TYPE_CMMB = 0x00050100;
+
+ /** The channel type for T-DMB (terrestrial). */
+ public static final int TYPE_T_DMB = 0x00060000;
+
+ /** The channel type for S-DMB (satellite). */
+ public static final int TYPE_S_DMB = 0x00060100;
+
+ /** A generic service type. */
+ public static final int SERVICE_TYPE_OTHER = 0x0;
+
+ /** The service type for regular TV channels that have both audio and video. */
+ public static final int SERVICE_TYPE_AUDIO_VIDEO = 0x1;
+
+ /** The service type for radio channels that have audio only. */
+ public static final int SERVICE_TYPE_AUDIO = 0x2;
+
+ /**
+ * The name of the {@link TvInputService} subclass that provides this TV channel. This
+ * should be a fully qualified class name (such as, "com.example.project.TvInputService").
+ * <p>
+ * This is a required field.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_SERVICE_NAME = "service_name";
+
+ /**
+ * The predefined type of this TV channel.
+ * <p>
+ * This is primarily used to indicate which broadcast standard (e.g. ATSC, DVB or ISDB) the
+ * current channel conforms to, with an exception being {@link #TYPE_PASSTHROUGH}, which is
+ * a special channel type used only by pass-through inputs such as HDMI. The value should
+ * match to one of the followings: {@link #TYPE_OTHER}, {@link #TYPE_PASSTHROUGH},
+ * {@link #TYPE_DVB_T}, {@link #TYPE_DVB_T2}, {@link #TYPE_DVB_S}, {@link #TYPE_DVB_S2},
+ * {@link #TYPE_DVB_C}, {@link #TYPE_DVB_C2}, {@link #TYPE_DVB_H}, {@link #TYPE_DVB_SH},
+ * {@link #TYPE_ATSC_T}, {@link #TYPE_ATSC_C}, {@link #TYPE_ATSC_M_H}, {@link #TYPE_ISDB_T},
+ * {@link #TYPE_ISDB_TB}, {@link #TYPE_ISDB_S}, {@link #TYPE_ISDB_C} {@link #TYPE_1SEG},
+ * {@link #TYPE_DTMB}, {@link #TYPE_CMMB}, {@link #TYPE_T_DMB}, {@link #TYPE_S_DMB}
+ * </p><p>
+ * This is a required field.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_TYPE = "type";
+
+ /**
+ * The predefined service type of this TV channel.
+ * <p>
+ * This is primarily used to indicate whether the current channel is a regular TV channel or
+ * a radio-like channel. Use the same coding for {@code service_type} in the underlying
+ * broadcast standard if it is defined there (e.g. ATSC A/53, ETSI EN 300 468 and ARIB
+ * STD-B10). Otherwise use one of the followings: {@link #SERVICE_TYPE_OTHER},
+ * {@link #SERVICE_TYPE_AUDIO_VIDEO}, {@link #SERVICE_TYPE_AUDIO}
+ * </p><p>
+ * This is a required field.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_SERVICE_TYPE = "service_type";
+
+ /**
+ * The original network ID of this TV channel.
+ * <p>
+ * This is used to identify the originating delivery system, if applicable. Use the same
+ * coding for {@code original_network_id} in the underlying broadcast standard if it is
+ * defined there (e.g. ETSI EN 300 468/TR 101 211 and ARIB STD-B10). If channels cannot be
+ * globally identified by 2-tuple {{@link #COLUMN_TRANSPORT_STREAM_ID},
+ * {@link #COLUMN_SERVICE_ID}}, one must carefully assign a value to this field to form a
+ * unique 3-tuple identification {{@link #COLUMN_ORIGINAL_NETWORK_ID},
+ * {@link #COLUMN_TRANSPORT_STREAM_ID}, {@link #COLUMN_SERVICE_ID}} for its channels.
+ * </p><p>
+ * This is a required field if the channel cannot be uniquely identified by a 2-tuple
+ * {{@link #COLUMN_TRANSPORT_STREAM_ID}, {@link #COLUMN_SERVICE_ID}}.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_ORIGINAL_NETWORK_ID = "original_network_id";
+
+ /**
+ * The transport stream ID of this channel.
+ * <p>
+ * This is used to identify the Transport Stream that contains the current channel from any
+ * other multiplex within a network, if applicable. Use the same coding for
+ * {@code transport_stream_id} defined in ISO/IEC 13818-1 if the channel is transmitted via
+ * the MPEG Transport Stream as is the case for many digital broadcast standards.
+ * </p><p>
+ * This is a required field if the current channel is transmitted via the MPEG Transport
+ * Stream.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id";
+
+ /**
+ * The service ID of this channel.
+ * <p>
+ * This is used to identify the current service (roughly equivalent to channel) from any
+ * other service within the Transport Stream, if applicable. Use the same coding for
+ * {@code service_id} in the underlying broadcast standard if it is defined there (e.g. ETSI
+ * EN 300 468 and ARIB STD-B10) or {@code program_number} (which usually has the same value
+ * as {@code service_id}) in ISO/IEC 13818-1 if the channel is transmitted via the MPEG
+ * Transport Stream.
+ * </p><p>
+ * This is a required field if the current channel is transmitted via the MPEG Transport
+ * Stream.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_SERVICE_ID = "service_id";
+
+ /**
+ * The channel number that is displayed to the user.
+ * <p>
+ * The format can vary depending on broadcast standard and product specification.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_DISPLAY_NUMBER = "display_number";
+
+ /**
+ * The channel name that is displayed to the user.
+ * <p>
+ * A call sign is a good candidate to use for this purpose but any name that helps the user
+ * recognize the current channel will be enough. Can also be empty depending on broadcast
+ * standard.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_DISPLAY_NAME = "display_name";
+
+ /**
+ * The description of this TV channel.
+ * <p>
+ * Can be empty initially.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_DESCRIPTION = "description";
+
+ /**
+ * The flag indicating whether this TV channel is browsable or not.
+ * <p>
+ * A value of 1 indicates the channel is included in the channel list that applications use
+ * to browse channels, a value of 0 indicates the channel is not included in the list. If
+ * not specified, this value is set to 1 (browsable) by default.
+ * </p><p>
+ * Type: INTEGER (boolean)
+ * </p>
+ */
+ public static final String COLUMN_BROWSABLE = "browsable";
+
+ /**
+ * The flag indicating whether this TV channel is searchable or not.
+ * <p>
+ * In some regions, it is not allowed to surface search results for a given channel without
+ * broadcaster's consent. This is used to impose such restriction. A value of 1 indicates
+ * the channel is searchable and can be included in search results, a value of 0 indicates
+ * the channel and its TV programs are hidden from search. If not specified, this value is
+ * set to 1 (searchable) by default.
+ * </p>
+ * <p>
+ * Type: INTEGER (boolean)
+ * </p>
+ */
+ public static final String COLUMN_SEARCHABLE = "searchable";
+
+ /**
+ * The flag indicating whether this TV channel is locked or not.
+ * <p>
+ * This is primarily used for alternative parental control to prevent unauthorized users
+ * from watching the current channel regardless of the content rating. A value of 1
+ * indicates the channel is locked and the user is required to enter passcode to unlock it
+ * in order to watch the current program from the channel, a value of 0 indicates the
+ * channel is not locked thus the user is not prompted to enter passcode If not specified,
+ * this value is set to 0 (not locked) by default.
+ * </p><p>
+ * Type: INTEGER (boolean)
+ * </p>
+ * @hide
+ */
+ public static final String COLUMN_LOCKED = "locked";
+
+ /**
+ * Internal data used by individual TV input services.
+ * <p>
+ * This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ * </p><p>
+ * Type: BLOB
+ * </p>
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
+
+ /**
+ * The version number of this row entry used by TV input services.
+ * <p>
+ * This is best used by sync adapters to identify the rows to update. The number can be
+ * defined by individual TV input services. One may assign the same value as
+ * {@code version_number} that appears in ETSI EN 300 468 or ATSC A/65, if the data are
+ * coming from a TV broadcast.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_VERSION_NUMBER = "version_number";
+
+ private Channels() {}
+ }
+
+ /** Column definitions for the TV programs table. */
+ public static final class Programs implements BaseTvColumns {
+
+ /** The content:// style URI for this table. */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"
+ + PATH_PROGRAM);
+
+ /** The MIME type of a directory of TV programs. */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/program";
+
+ /** The MIME type of a single TV program. */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/program";
+
+ /**
+ * The ID of the TV channel that contains this TV program.
+ * <p>
+ * This is a part of the channel URI and matches to {@link BaseColumns#_ID}.
+ * </p><p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String COLUMN_CHANNEL_ID = "channel_id";
+
+ /**
+ * The title of this TV program.
+ * <p>
+ * Type: TEXT
+ * </p>
+ **/
+ public static final String COLUMN_TITLE = "title";
+
+ /**
+ * The start time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
+
+ /**
+ * The end time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
+
+ /**
+ * The comma-separated genre string of this TV program.
+ * <p>
+ * Use the same language appeared in the underlying broadcast standard, if applicable. (For
+ * example, one can refer to the genre strings used in Genre Descriptor of ATSC A/65 or
+ * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, leave empty.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_BROADCAST_GENRE = "broadcast_genre";
+
+ /**
+ * The comma-separated canonical genre string of this TV program.
+ * <p>
+ * Canonical genres are defined in {@link Genres}. Use {@link Genres#encode Genres.encode()}
+ * to create a text that can be stored in this column. Use {@link Genres#decode
+ * Genres.decode()} to get the canonical genre strings from the text stored in this column.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ * @see Genres
+ */
+ public static final String COLUMN_CANONICAL_GENRE = "canonical_genre";
+
+ /**
+ * The short description of this TV program that is displayed to the user by default.
+ * <p>
+ * It is recommended to limit the length of the descriptions to 256 characters.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+
+ /**
+ * The detailed, lengthy description of this TV program that is displayed only when the user
+ * wants to see more information.
+ * <p>
+ * TV input services should leave this field empty if they have no additional details beyond
+ * {@link #COLUMN_SHORT_DESCRIPTION}.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_LONG_DESCRIPTION = "long_description";
+
+ /**
+ * The comma-separated audio languages of this TV program.
+ * <p>
+ * This is used to describe available audio languages included in the program. Use
+ * 3-character language code as specified by ISO 639-2.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_AUDIO_LANGUAGE = "audio_language";
+
+ /**
+ * Internal data used by individual TV input services.
+ * <p>
+ * This is internal to the provider that inserted it, and should not be decoded by other
+ * apps.
+ * </p><p>
+ * Type: BLOB
+ * </p>
+ */
+ public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
+
+ /**
+ * The version number of this row entry used by TV input services.
+ * <p>
+ * This is best used by sync adapters to identify the rows to update. The number can be
+ * defined by individual TV input services. One may assign the same value as
+ * {@code version_number} in ETSI EN 300 468 or ATSC A/65, if the data are coming from a TV
+ * broadcast.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String COLUMN_VERSION_NUMBER = "version_number";
+
+ private Programs() {}
+
+ /** Canonical genres for TV programs. */
+ public static final class Genres {
+ /** The genre for Family/Kids. */
+ public static final String FAMILY_KIDS = "Family/Kids";
+
+ /** The genre for Sports. */
+ public static final String SPORTS = "Sports";
+
+ /** The genre for Shopping. */
+ public static final String SHOPPING = "Shopping";
+
+ /** The genre for Movies. */
+ public static final String MOVIES = "Movies";
+
+ /** The genre for Comedy. */
+ public static final String COMEDY = "Comedy";
+
+ /** The genre for Travel. */
+ public static final String TRAVEL = "Travel";
+
+ /** The genre for Drama. */
+ public static final String DRAMA = "Drama";
+
+ /** The genre for Education. */
+ public static final String EDUCATION = "Education";
+
+ /** The genre for Animal/Wildlife. */
+ public static final String ANIMAL_WILDLIFE = "Animal/Wildlife";
+
+ /** The genre for News. */
+ public static final String NEWS = "News";
+
+ /** The genre for Gaming. */
+ public static final String GAMING = "Gaming";
+
+ private Genres() {}
+
+ /**
+ * Encodes canonical genre strings to a text that can be put into the database.
+ *
+ * @param genres Canonical genre strings. Use the strings defined in this class.
+ * @return an encoded genre string that can be inserted into the
+ * {@link #COLUMN_CANONICAL_GENRE} column.
+ */
+ public static String encode(String... genres) {
+ StringBuilder sb = new StringBuilder();
+ String separator = "";
+ for (String genre : genres) {
+ sb.append(separator).append(genre);
+ separator = ",";
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Decodes the canonical genre strings from the text stored in the database.
+ *
+ * @param genres The encoded genre string retrieved from the
+ * {@link #COLUMN_CANONICAL_GENRE} column.
+ * @return canonical genre strings.
+ */
+ public static String[] decode(String genres) {
+ return genres.split("\\s*,\\s*");
+ }
+ }
+ }
+
+ /**
+ * Column definitions for the TV programs that the user watched. Applications do not have access
+ * to this table.
+ *
+ * @hide
+ */
+ public static final class WatchedPrograms implements BaseColumns {
+
+ /** The content:// style URI for this table. */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://" + AUTHORITY + "/watched_program");
+
+ /** The MIME type of a directory of watched programs. */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/watched_program";
+
+ /** The MIME type of a single item in this table. */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/watched_program";
+
+ /**
+ * The UTC time that the user started watching this TV program, in milliseconds since the
+ * epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String COLUMN_WATCH_START_TIME_UTC_MILLIS =
+ "watch_start_time_utc_millis";
+
+ /**
+ * The UTC time that the user stopped watching this TV program, in milliseconds since the
+ * epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String COLUMN_WATCH_END_TIME_UTC_MILLIS = "watch_end_time_utc_millis";
+
+ /**
+ * The channel ID that contains this TV program.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String COLUMN_CHANNEL_ID = "channel_id";
+
+ /**
+ * The title of this TV program.
+ * <p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_TITLE = "title";
+
+ /**
+ * The start time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
+
+ /**
+ * The end time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
+
+ /**
+ * The description of this TV program.
+ * <p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String COLUMN_DESCRIPTION = "description";
+
+ private WatchedPrograms() {}
+ }
+}
diff --git a/media/java/android/media/tv/TvInputHardwareInfo.aidl b/media/java/android/media/tv/TvInputHardwareInfo.aidl
new file mode 100644
index 0000000..a4c38bb
--- /dev/null
+++ b/media/java/android/media/tv/TvInputHardwareInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ *
+ * Copyright 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.media.tv;
+
+parcelable TvInputHardwareInfo;
diff --git a/media/java/android/media/tv/TvInputHardwareInfo.java b/media/java/android/media/tv/TvInputHardwareInfo.java
new file mode 100644
index 0000000..4beb960
--- /dev/null
+++ b/media/java/android/media/tv/TvInputHardwareInfo.java
@@ -0,0 +1,93 @@
+/*
+ * 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.media.tv;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Simple container for information about TV input hardware.
+ * Not for third-party developers.
+ *
+ * @hide
+ */
+public final class TvInputHardwareInfo implements Parcelable {
+ static final String TAG = "TvInputHardwareInfo";
+
+ // Match hardware/libhardware/include/hardware/tv_input.h
+ public static final int TV_INPUT_TYPE_HDMI = 1;
+ public static final int TV_INPUT_TYPE_BUILT_IN_TUNER = 2;
+ public static final int TV_INPUT_TYPE_PASSTHROUGH = 3;
+
+ public static final Parcelable.Creator<TvInputHardwareInfo> CREATOR =
+ new Parcelable.Creator<TvInputHardwareInfo>() {
+ @Override
+ public TvInputHardwareInfo createFromParcel(Parcel source) {
+ try {
+ TvInputHardwareInfo info = new TvInputHardwareInfo();
+ info.readFromParcel(source);
+ return info;
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating TvInputHardwareInfo from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public TvInputHardwareInfo[] newArray(int size) {
+ return new TvInputHardwareInfo[size];
+ }
+ };
+
+ private int mDeviceId;
+ private int mType;
+ // TODO: Add audio port & audio address for audio service.
+ // TODO: Add HDMI handle for HDMI service.
+
+ public TvInputHardwareInfo() { }
+
+ public TvInputHardwareInfo(int deviceId, int type) {
+ mDeviceId = deviceId;
+ mType = type;
+ }
+
+ public int getDeviceId() {
+ return mDeviceId;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ // Parcelable
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mDeviceId);
+ dest.writeInt(mType);
+ }
+
+ public void readFromParcel(Parcel source) {
+ mDeviceId = source.readInt();
+ mType = source.readInt();
+ }
+}
diff --git a/media/java/android/media/tv/TvInputInfo.aidl b/media/java/android/media/tv/TvInputInfo.aidl
new file mode 100644
index 0000000..ba139a2
--- /dev/null
+++ b/media/java/android/media/tv/TvInputInfo.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.media.tv;
+
+parcelable TvInputInfo;
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
new file mode 100644
index 0000000..ed599ed
--- /dev/null
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -0,0 +1,280 @@
+/*
+ * 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.media.tv;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * This class is used to specify meta information of a TV input.
+ */
+public final class TvInputInfo implements Parcelable {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "TvInputInfo";
+
+ /**
+ * The name of the TV input service to provide to the setup activity and settings activity.
+ */
+ public static final String EXTRA_SERVICE_NAME = "serviceName";
+
+ private static final String XML_START_TAG_NAME = "tv-input";
+
+ private final ResolveInfo mService;
+ private final String mId;
+
+ // Attributes from XML meta data.
+ private String mSetupActivity;
+ private String mSettingsActivity;
+
+ /**
+ * Create a new instance of the TvInputInfo class,
+ * instantiating it from the given Context and ResolveInfo.
+ *
+ * @param service The ResolveInfo returned from the package manager about this TV input service.
+ * @hide */
+ public static TvInputInfo createTvInputInfo(Context context, ResolveInfo service)
+ throws XmlPullParserException, IOException {
+ ServiceInfo si = service.serviceInfo;
+ PackageManager pm = context.getPackageManager();
+ XmlResourceParser parser = null;
+ try {
+ parser = si.loadXmlMetaData(pm, TvInputService.SERVICE_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + TvInputService.SERVICE_META_DATA
+ + " meta-data for " + si.name);
+ }
+
+ Resources res = pm.getResourcesForApplication(si.applicationInfo);
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ }
+
+ String nodeName = parser.getName();
+ if (!XML_START_TAG_NAME.equals(nodeName)) {
+ throw new XmlPullParserException(
+ "Meta-data does not start with tv-input-service tag in " + si.name);
+ }
+
+ TvInputInfo input = new TvInputInfo(context, service);
+ TypedArray sa = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.TvInputService);
+ input.mSetupActivity = sa.getString(
+ com.android.internal.R.styleable.TvInputService_setupActivity);
+ if (DEBUG) {
+ Log.d(TAG, "Setup activity loaded. [" + input.mSetupActivity + "] for " + si.name);
+ }
+ input.mSettingsActivity = sa.getString(
+ com.android.internal.R.styleable.TvInputService_settingsActivity);
+ if (DEBUG) {
+ Log.d(TAG, "Settings activity loaded. [" + input.mSettingsActivity + "] for "
+ + si.name);
+ }
+ sa.recycle();
+
+ return input;
+ } catch (NameNotFoundException e) {
+ throw new XmlPullParserException("Unable to create context for: " + si.packageName);
+ } finally {
+ if (parser != null) {
+ parser.close();
+ }
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param service The ResolveInfo returned from the package manager about this TV input service.
+ * @hide
+ */
+ private TvInputInfo(Context context, ResolveInfo service) {
+ mService = service;
+ ServiceInfo si = service.serviceInfo;
+ mId = generateInputIdForComponentName(new ComponentName(si.packageName, si.name));
+ }
+
+ /**
+ * Returns a unique ID for this TV input. The ID is generated from the package and class name
+ * implementing the TV input service.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the .apk package that implements this TV input service.
+ */
+ public String getPackageName() {
+ return mService.serviceInfo.packageName;
+ }
+
+ /**
+ * Returns the class name of the service component that implements this TV input service.
+ */
+ public String getServiceName() {
+ return mService.serviceInfo.name;
+ }
+
+ /**
+ * Returns the component of the service that implements this TV input.
+ */
+ public ComponentName getComponent() {
+ return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
+ }
+
+ /**
+ * Returns an intent to start the setup activity for this TV input service.
+ */
+ public Intent getIntentForSetupActivity() {
+ if (!TextUtils.isEmpty(mSetupActivity)) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(getPackageName(), mSetupActivity);
+ intent.putExtra(EXTRA_SERVICE_NAME, getServiceName());
+ return intent;
+ }
+ return null;
+ }
+
+ /**
+ * Returns an intent to start the settings activity for this TV input service.
+ */
+ public Intent getIntentForSettingsActivity() {
+ if (!TextUtils.isEmpty(mSettingsActivity)) {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(getPackageName(), mSettingsActivity);
+ intent.putExtra(EXTRA_SERVICE_NAME, getServiceName());
+ return intent;
+ }
+ return null;
+ }
+
+ /**
+ * Loads the user-displayed label for this TV input service.
+ *
+ * @param pm Supplies a PackageManager used to load the TV input's resources.
+ * @return a CharSequence containing the TV input's label. If the TV input does not have
+ * a label, its name is returned.
+ */
+ public CharSequence loadLabel(PackageManager pm) {
+ return mService.loadLabel(pm);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return mId.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+
+ if (!(o instanceof TvInputInfo)) {
+ return false;
+ }
+
+ TvInputInfo obj = (TvInputInfo) o;
+ return mId.equals(obj.mId)
+ && mService.serviceInfo.packageName.equals(obj.mService.serviceInfo.packageName)
+ && mService.serviceInfo.name.equals(obj.mService.serviceInfo.name);
+ }
+
+ @Override
+ public String toString() {
+ return "TvInputInfo{id=" + mId
+ + ", pkg=" + mService.serviceInfo.packageName
+ + ", service=" + mService.serviceInfo.name + "}";
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mId);
+ mService.writeToParcel(dest, flags);
+ dest.writeString(mSetupActivity);
+ dest.writeString(mSettingsActivity);
+ }
+
+ /**
+ * Used to generate an input id from a ComponentName.
+ *
+ * @param name the component name for generating an input id.
+ * @return the generated input id for the given {@code name}.
+ * @hide
+ */
+ public static final String generateInputIdForComponentName(ComponentName name) {
+ return name.flattenToShortString();
+ }
+
+ /**
+ * Used to make this class parcelable.
+ *
+ * @hide
+ */
+ public static final Parcelable.Creator<TvInputInfo> CREATOR =
+ new Parcelable.Creator<TvInputInfo>() {
+ @Override
+ public TvInputInfo createFromParcel(Parcel in) {
+ return new TvInputInfo(in);
+ }
+
+ @Override
+ public TvInputInfo[] newArray(int size) {
+ return new TvInputInfo[size];
+ }
+ };
+
+ private TvInputInfo(Parcel in) {
+ mId = in.readString();
+ mService = ResolveInfo.CREATOR.createFromParcel(in);
+ mSetupActivity = in.readString();
+ mSettingsActivity = in.readString();
+ }
+}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
new file mode 100644
index 0000000..698a861
--- /dev/null
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -0,0 +1,917 @@
+/*
+ * 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.media.tv;
+
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
+import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
+import android.view.Surface;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Central system API to the overall TV input framework (TIF) architecture, which arbitrates
+ * interaction between applications and the selected TV inputs.
+ */
+public final class TvInputManager {
+ private static final String TAG = "TvInputManager";
+
+ private final ITvInputManager mService;
+
+ // A mapping from an input to the list of its TvInputListenerRecords.
+ private final Map<String, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
+ new HashMap<String, List<TvInputListenerRecord>>();
+
+ // A mapping from the sequence number of a session to its SessionCallbackRecord.
+ private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
+ new SparseArray<SessionCallbackRecord>();
+
+ // A sequence number for the next session to be created. Should be protected by a lock
+ // {@code mSessionCallbackRecordMap}.
+ private int mNextSeq;
+
+ private final ITvInputClient mClient;
+
+ private final int mUserId;
+
+ /**
+ * Interface used to receive the created session.
+ */
+ public abstract static class SessionCallback {
+ /**
+ * This is called after {@link TvInputManager#createSession} has been processed.
+ *
+ * @param session A {@link TvInputManager.Session} instance created. This can be
+ * {@code null} if the creation request failed.
+ */
+ public void onSessionCreated(Session session) {
+ }
+
+ /**
+ * This is called when {@link TvInputManager.Session} is released.
+ * This typically happens when the process hosting the session has crashed or been killed.
+ *
+ * @param session A {@link TvInputManager.Session} instance released.
+ */
+ public void onSessionReleased(Session session) {
+ }
+
+ /**
+ * This is called at the beginning of the playback of a channel and later when the format of
+ * the video stream has been changed.
+ *
+ * @param session A {@link TvInputManager.Session} associated with this callback
+ * @param width The width of the video.
+ * @param height The height of the video.
+ * @param interlaced whether the video is interlaced mode or planer mode.
+ * @hide
+ */
+ public void onVideoStreamChanged(Session session, int width, int height,
+ boolean interlaced) {
+ }
+
+ /**
+ * This is called at the beginning of the playback of a channel and later when the format of
+ * the audio stream has been changed.
+ *
+ * @param session A {@link TvInputManager.Session} associated with this callback
+ * @param channelCount The number of channels in the audio stream.
+ * @hide
+ */
+ public void onAudioStreamChanged(Session session, int channelCount) {
+ }
+
+ /**
+ * This is called at the beginning of the playback of a channel and later when the closed
+ * caption stream has been changed.
+ *
+ * @param session A {@link TvInputManager.Session} associated with this callback
+ * @param hasClosedCaption Whether the stream has closed caption or not.
+ * @hide
+ */
+ public void onClosedCaptionStreamChanged(Session session, boolean hasClosedCaption) {
+ }
+
+ /**
+ * This is called when a custom event has been sent from this session.
+ *
+ * @param session A {@link TvInputManager.Session} associated with this callback
+ * @param eventType The type of the event.
+ * @param eventArgs Optional arguments of the event.
+ * @hide
+ */
+ public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
+ }
+ }
+
+ private static final class SessionCallbackRecord {
+ private final SessionCallback mSessionCallback;
+ private final Handler mHandler;
+ private Session mSession;
+
+ public SessionCallbackRecord(SessionCallback sessionCallback,
+ Handler handler) {
+ mSessionCallback = sessionCallback;
+ mHandler = handler;
+ }
+
+ public void postSessionCreated(final Session session) {
+ mSession = session;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onSessionCreated(session);
+ }
+ });
+ }
+
+ public void postSessionReleased() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onSessionReleased(mSession);
+ }
+ });
+ }
+
+ public void postVideoStreamChanged(final int width, final int height,
+ final boolean interlaced) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onVideoStreamChanged(mSession, width, height, interlaced);
+ }
+ });
+ }
+
+ public void postAudioStreamChanged(final int channelCount) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onAudioStreamChanged(mSession, channelCount);
+ }
+ });
+ }
+
+ public void postClosedCaptionStreamChanged(final boolean hasClosedCaption) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onClosedCaptionStreamChanged(mSession, hasClosedCaption);
+ }
+ });
+ }
+
+ public void postSessionEvent(final String eventType, final Bundle eventArgs) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onSessionEvent(mSession, eventType, eventArgs);
+ }
+ });
+ }
+ }
+
+ /**
+ * Interface used to monitor status of the TV input.
+ */
+ public abstract static class TvInputListener {
+ /**
+ * This is called when the availability status of a given TV input is changed.
+ *
+ * @param inputId the id of the TV input.
+ * @param isAvailable {@code true} if the given TV input is available to show TV programs.
+ * {@code false} otherwise.
+ */
+ public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+ }
+ }
+
+ private static final class TvInputListenerRecord {
+ private final TvInputListener mListener;
+ private final Handler mHandler;
+
+ public TvInputListenerRecord(TvInputListener listener, Handler handler) {
+ mListener = listener;
+ mHandler = handler;
+ }
+
+ public TvInputListener getListener() {
+ return mListener;
+ }
+
+ public void postAvailabilityChanged(final String inputId, final boolean isAvailable) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onAvailabilityChanged(inputId, isAvailable);
+ }
+ });
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public TvInputManager(ITvInputManager service, int userId) {
+ mService = service;
+ mUserId = userId;
+ mClient = new ITvInputClient.Stub() {
+ @Override
+ public void onSessionCreated(String inputId, IBinder token, InputChannel channel,
+ int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for " + token);
+ return;
+ }
+ Session session = null;
+ if (token != null) {
+ session = new Session(token, channel, mService, mUserId, seq,
+ mSessionCallbackRecordMap);
+ }
+ record.postSessionCreated(session);
+ }
+ }
+
+ @Override
+ public void onSessionReleased(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ mSessionCallbackRecordMap.delete(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq:" + seq);
+ return;
+ }
+ record.mSession.releaseInternal();
+ record.postSessionReleased();
+ }
+ }
+
+ @Override
+ public void onVideoStreamChanged(int width, int height, boolean interlaced, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postVideoStreamChanged(width, height, interlaced);
+ }
+ }
+
+ @Override
+ public void onAudioStreamChanged(int channelCount, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postAudioStreamChanged(channelCount);
+ }
+ }
+
+ @Override
+ public void onClosedCaptionStreamChanged(boolean hasClosedCaption, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postClosedCaptionStreamChanged(hasClosedCaption);
+ }
+ }
+
+ @Override
+ public void onSessionEvent(String eventType, Bundle eventArgs, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postSessionEvent(eventType, eventArgs);
+ }
+ }
+
+ @Override
+ public void onAvailabilityChanged(String inputId, boolean isAvailable) {
+ synchronized (mTvInputListenerRecordsMap) {
+ List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
+ if (records == null) {
+ // Silently ignore - no listener is registered yet.
+ return;
+ }
+ int recordsCount = records.size();
+ for (int i = 0; i < recordsCount; i++) {
+ records.get(i).postAvailabilityChanged(inputId, isAvailable);
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns the complete list of TV inputs on the system.
+ *
+ * @return List of {@link TvInputInfo} for each TV input that describes its meta information.
+ */
+ public List<TvInputInfo> getTvInputList() {
+ try {
+ return mService.getTvInputList(mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Returns the availability of a given TV input.
+ *
+ * @param inputId the id of the TV input.
+ * @throws IllegalArgumentException if the argument is {@code null}.
+ * @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
+ * TV input.
+ */
+ public boolean getAvailability(String inputId) {
+ if (inputId == null) {
+ throw new IllegalArgumentException("id cannot be null");
+ }
+ synchronized (mTvInputListenerRecordsMap) {
+ List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
+ if (records == null || records.size() == 0) {
+ throw new IllegalStateException("At least one listener should be registered.");
+ }
+ }
+ try {
+ return mService.getAvailability(mClient, inputId, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Registers a {@link TvInputListener} for a given TV input.
+ *
+ * @param inputId the id of the TV input.
+ * @param listener a listener used to monitor status of the given TV input.
+ * @param handler a {@link Handler} that the status change will be delivered to.
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ */
+ public void registerListener(String inputId, TvInputListener listener, Handler handler) {
+ if (inputId == null) {
+ throw new IllegalArgumentException("id cannot be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener cannot be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler cannot be null");
+ }
+ synchronized (mTvInputListenerRecordsMap) {
+ List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
+ if (records == null) {
+ records = new ArrayList<TvInputListenerRecord>();
+ mTvInputListenerRecordsMap.put(inputId, records);
+ try {
+ mService.registerCallback(mClient, inputId, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ records.add(new TvInputListenerRecord(listener, handler));
+ }
+ }
+
+ /**
+ * Unregisters the existing {@link TvInputListener} for a given TV input.
+ *
+ * @param inputId the id of the TV input.
+ * @param listener the existing listener to remove for the given TV input.
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ */
+ public void unregisterListener(String inputId, final TvInputListener listener) {
+ if (inputId == null) {
+ throw new IllegalArgumentException("id cannot be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener cannot be null");
+ }
+ synchronized (mTvInputListenerRecordsMap) {
+ List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
+ if (records == null) {
+ Log.e(TAG, "No listener found for " + inputId);
+ return;
+ }
+ for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
+ TvInputListenerRecord record = it.next();
+ if (record.getListener() == listener) {
+ it.remove();
+ }
+ }
+ if (records.isEmpty()) {
+ try {
+ mService.unregisterCallback(mClient, inputId, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ } finally {
+ mTvInputListenerRecordsMap.remove(inputId);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a {@link Session} for a given TV input.
+ * <p>
+ * The number of sessions that can be created at the same time is limited by the capability of
+ * the given TV input.
+ * </p>
+ *
+ * @param inputId the id of the TV input.
+ * @param callback a callback used to receive the created session.
+ * @param handler a {@link Handler} that the session creation will be delivered to.
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ */
+ public void createSession(String inputId, final SessionCallback callback,
+ Handler handler) {
+ if (inputId == null) {
+ throw new IllegalArgumentException("id cannot be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler cannot be null");
+ }
+ SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
+ synchronized (mSessionCallbackRecordMap) {
+ int seq = mNextSeq++;
+ mSessionCallbackRecordMap.put(seq, record);
+ try {
+ mService.createSession(mClient, inputId, seq, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /** The Session provides the per-session functionality of TV inputs. */
+ public static final class Session {
+ static final int DISPATCH_IN_PROGRESS = -1;
+ static final int DISPATCH_NOT_HANDLED = 0;
+ static final int DISPATCH_HANDLED = 1;
+
+ private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
+
+ private final ITvInputManager mService;
+ private final int mUserId;
+ private final int mSeq;
+
+ // For scheduling input event handling on the main thread. This also serves as a lock to
+ // protect pending input events and the input channel.
+ private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
+
+ private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20);
+ private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20);
+ private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
+
+ private IBinder mToken;
+ private TvInputEventSender mSender;
+ private InputChannel mChannel;
+
+ /** @hide */
+ private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
+ int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
+ mToken = token;
+ mChannel = channel;
+ mService = service;
+ mUserId = userId;
+ mSeq = seq;
+ mSessionCallbackRecordMap = sessionCallbackRecordMap;
+ }
+
+ /**
+ * Releases this session.
+ *
+ * @throws IllegalStateException if the session has been already released.
+ */
+ public void release() {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.releaseSession(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ releaseInternal();
+ }
+
+ /**
+ * Sets the {@link android.view.Surface} for this session.
+ *
+ * @param surface A {@link android.view.Surface} used to render video.
+ * @throws IllegalStateException if the session has been already released.
+ * @hide
+ */
+ public void setSurface(Surface surface) {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ // surface can be null.
+ try {
+ mService.setSurface(mToken, surface, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets the relative volume of this session to handle a change of audio focus.
+ *
+ * @param volume A volume value between 0.0f to 1.0f.
+ * @throws IllegalArgumentException if the volume value is out of range.
+ * @throws IllegalStateException if the session has been already released.
+ */
+ public void setVolume(float volume) {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ if (volume < 0.0f || volume > 1.0f) {
+ throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
+ }
+ mService.setVolume(mToken, volume, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Tunes to a given channel.
+ *
+ * @param channelUri The URI of a channel.
+ * @throws IllegalArgumentException if the argument is {@code null}.
+ * @throws IllegalStateException if the session has been already released.
+ */
+ public void tune(Uri channelUri) {
+ if (channelUri == null) {
+ throw new IllegalArgumentException("channelUri cannot be null");
+ }
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.tune(mToken, channelUri, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView}
+ * should be called whenever the layout of its containing view is changed.
+ * {@link #removeOverlayView()} should be called to remove the overlay view.
+ * Since a session can have only one overlay view, this method should be called only once
+ * or it can be called again after calling {@link #removeOverlayView()}.
+ *
+ * @param view A view playing TV.
+ * @param frame A position of the overlay view.
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ * @throws IllegalStateException if {@code view} is not attached to a window or
+ * if the session has been already released.
+ */
+ void createOverlayView(View view, Rect frame) {
+ if (view == null) {
+ throw new IllegalArgumentException("view cannot be null");
+ }
+ if (frame == null) {
+ throw new IllegalArgumentException("frame cannot be null");
+ }
+ if (view.getWindowToken() == null) {
+ throw new IllegalStateException("view must be attached to a window");
+ }
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Relayouts the current overlay view.
+ *
+ * @param frame A new position of the overlay view.
+ * @throws IllegalArgumentException if the arguments is {@code null}.
+ * @throws IllegalStateException if the session has been already released.
+ */
+ void relayoutOverlayView(Rect frame) {
+ if (frame == null) {
+ throw new IllegalArgumentException("frame cannot be null");
+ }
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.relayoutOverlayView(mToken, frame, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Removes the current overlay view.
+ *
+ * @throws IllegalStateException if the session has been already released.
+ */
+ void removeOverlayView() {
+ if (mToken == null) {
+ throw new IllegalStateException("the session has been already released");
+ }
+ try {
+ mService.removeOverlayView(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Dispatches an input event to this session.
+ *
+ * @param event {@link InputEvent} to dispatch.
+ * @param token A token used to identify the input event later in the callback.
+ * @param callback A callback used to receive the dispatch result.
+ * @param handler {@link Handler} that the dispatch result will be delivered to.
+ * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
+ * {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
+ * {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
+ * be invoked later.
+ * @throws IllegalArgumentException if any of the necessary arguments is {@code null}.
+ * @hide
+ */
+ public int dispatchInputEvent(InputEvent event, Object token,
+ FinishedInputEventCallback callback, Handler handler) {
+ if (event == null) {
+ throw new IllegalArgumentException("event cannot be null");
+ }
+ if (callback != null && handler == null) {
+ throw new IllegalArgumentException("handler cannot be null");
+ }
+ synchronized (mHandler) {
+ if (mChannel == null) {
+ return DISPATCH_NOT_HANDLED;
+ }
+ PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ // Already running on the main thread so we can send the event immediately.
+ return sendInputEventOnMainLooperLocked(p);
+ }
+
+ // Post the event to the main thread.
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ return DISPATCH_IN_PROGRESS;
+ }
+ }
+
+ /**
+ * Callback that is invoked when an input event that was dispatched to this session has been
+ * finished.
+ *
+ * @hide
+ */
+ public interface FinishedInputEventCallback {
+ /**
+ * Called when the dispatched input event is finished.
+ *
+ * @param token a token passed to {@link #dispatchInputEvent}.
+ * @param handled {@code true} if the dispatched input event was handled properly.
+ * {@code false} otherwise.
+ */
+ public void onFinishedInputEvent(Object token, boolean handled);
+ }
+
+ // Must be called on the main looper
+ private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+ synchronized (mHandler) {
+ int result = sendInputEventOnMainLooperLocked(p);
+ if (result == DISPATCH_IN_PROGRESS) {
+ return;
+ }
+ }
+
+ invokeFinishedInputEventCallback(p, false);
+ }
+
+ private int sendInputEventOnMainLooperLocked(PendingEvent p) {
+ if (mChannel != null) {
+ if (mSender == null) {
+ mSender = new TvInputEventSender(mChannel, mHandler.getLooper());
+ }
+
+ final InputEvent event = p.mEvent;
+ final int seq = event.getSequenceNumber();
+ if (mSender.sendInputEvent(seq, event)) {
+ mPendingEvents.put(seq, p);
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
+ return DISPATCH_IN_PROGRESS;
+ }
+
+ Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
+ + event);
+ }
+ return DISPATCH_NOT_HANDLED;
+ }
+
+ void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+ final PendingEvent p;
+ synchronized (mHandler) {
+ int index = mPendingEvents.indexOfKey(seq);
+ if (index < 0) {
+ return; // spurious, event already finished or timed out
+ }
+
+ p = mPendingEvents.valueAt(index);
+ mPendingEvents.removeAt(index);
+
+ if (timeout) {
+ Log.w(TAG, "Timeout waiting for seesion to handle input event after "
+ + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
+ } else {
+ mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+ }
+ }
+
+ invokeFinishedInputEventCallback(p, handled);
+ }
+
+ // Assumes the event has already been removed from the queue.
+ void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+ p.mHandled = handled;
+ if (p.mHandler.getLooper().isCurrentThread()) {
+ // Already running on the callback handler thread so we can send the callback
+ // immediately.
+ p.run();
+ } else {
+ // Post the event to the callback handler thread.
+ // In this case, the callback will be responsible for recycling the event.
+ Message msg = Message.obtain(p.mHandler, p);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ private void flushPendingEventsLocked() {
+ mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
+
+ final int count = mPendingEvents.size();
+ for (int i = 0; i < count; i++) {
+ int seq = mPendingEvents.keyAt(i);
+ Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
+ FinishedInputEventCallback callback, Handler handler) {
+ PendingEvent p = mPendingEventPool.acquire();
+ if (p == null) {
+ p = new PendingEvent();
+ }
+ p.mEvent = event;
+ p.mToken = token;
+ p.mCallback = callback;
+ p.mHandler = handler;
+ return p;
+ }
+
+ private void recyclePendingEventLocked(PendingEvent p) {
+ p.recycle();
+ mPendingEventPool.release(p);
+ }
+
+ private void releaseInternal() {
+ mToken = null;
+ synchronized (mHandler) {
+ if (mChannel != null) {
+ if (mSender != null) {
+ flushPendingEventsLocked();
+ mSender.dispose();
+ mSender = null;
+ }
+ mChannel.dispose();
+ mChannel = null;
+ }
+ }
+ synchronized (mSessionCallbackRecordMap) {
+ mSessionCallbackRecordMap.remove(mSeq);
+ }
+ }
+
+ private final class InputEventHandler extends Handler {
+ public static final int MSG_SEND_INPUT_EVENT = 1;
+ public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
+ public static final int MSG_FLUSH_INPUT_EVENT = 3;
+
+ InputEventHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SEND_INPUT_EVENT: {
+ sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
+ return;
+ }
+ case MSG_TIMEOUT_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, true);
+ return;
+ }
+ case MSG_FLUSH_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, false);
+ return;
+ }
+ }
+ }
+ }
+
+ private final class TvInputEventSender extends InputEventSender {
+ public TvInputEventSender(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEventFinished(int seq, boolean handled) {
+ finishedInputEvent(seq, handled, false);
+ }
+ }
+
+ private final class PendingEvent implements Runnable {
+ public InputEvent mEvent;
+ public Object mToken;
+ public FinishedInputEventCallback mCallback;
+ public Handler mHandler;
+ public boolean mHandled;
+
+ public void recycle() {
+ mEvent = null;
+ mToken = null;
+ mCallback = null;
+ mHandler = null;
+ mHandled = false;
+ }
+
+ @Override
+ public void run() {
+ mCallback.onFinishedInputEvent(mToken, mHandled);
+
+ synchronized (mHandler) {
+ recyclePendingEventLocked(this);
+ }
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
new file mode 100644
index 0000000..8ba0e20
--- /dev/null
+++ b/media/java/android/media/tv/TvInputService.java
@@ -0,0 +1,651 @@
+/*
+ * 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.media.tv;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.media.tv.ITvInputService;
+import android.media.tv.TvInputManager.Session;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
+
+/**
+ * A base class for implementing television input service.
+ */
+public abstract class TvInputService extends Service {
+ // STOPSHIP: Turn debugging off.
+ private static final boolean DEBUG = true;
+ private static final String TAG = "TvInputService";
+
+ /**
+ * This is the interface name that a service implementing a TV input should say that it support
+ * -- that is, this is the action it uses for its intent filter. To be supported, the service
+ * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that
+ * other applications cannot abuse it.
+ */
+ public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
+
+ /**
+ * Name under which a TvInputService component publishes information about itself.
+ * This meta-data must reference an XML resource containing an
+ * <code>&lt;{@link android.R.styleable#TvInputService tv-input}&gt;</code>
+ * tag.
+ */
+ public static final String SERVICE_META_DATA = "android.media.tv.input";
+
+ private String mId;
+ private final Handler mHandler = new ServiceHandler();
+ private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
+ new RemoteCallbackList<ITvInputServiceCallback>();
+ private boolean mAvailable;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mId = TvInputInfo.generateInputIdForComponentName(
+ new ComponentName(getPackageName(), getClass().getName()));
+ }
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return new ITvInputService.Stub() {
+ @Override
+ public void registerCallback(ITvInputServiceCallback cb) {
+ if (cb != null) {
+ mCallbacks.register(cb);
+ // The first time a callback is registered, the service needs to report its
+ // availability status so that the system can know its initial value.
+ try {
+ cb.onAvailabilityChanged(mId, mAvailable);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onAvailabilityChanged", e);
+ }
+ }
+ }
+
+ @Override
+ public void unregisterCallback(ITvInputServiceCallback cb) {
+ if (cb != null) {
+ mCallbacks.unregister(cb);
+ }
+ }
+
+ @Override
+ public void createSession(InputChannel channel, ITvInputSessionCallback cb) {
+ if (channel == null) {
+ Log.w(TAG, "Creating session without input channel");
+ }
+ if (cb == null) {
+ return;
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = channel;
+ args.arg2 = cb;
+ mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
+ }
+ };
+ }
+
+ /**
+ * Convenience method to notify an availability change of this TV input service.
+ *
+ * @param available {@code true} if the input service is available to show TV programs.
+ */
+ public final void setAvailable(boolean available) {
+ if (available != mAvailable) {
+ mAvailable = available;
+ mHandler.obtainMessage(ServiceHandler.DO_BROADCAST_AVAILABILITY_CHANGE, available)
+ .sendToTarget();
+ }
+ }
+
+ /**
+ * Get the number of callbacks that are registered.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public final int getRegisteredCallbackCount() {
+ return mCallbacks.getRegisteredCallbackCount();
+ }
+
+ /**
+ * Returns a concrete implementation of {@link TvInputSessionImpl}.
+ * <p>
+ * May return {@code null} if this TV input service fails to create a session for some reason.
+ * </p>
+ */
+ public abstract TvInputSessionImpl onCreateSession();
+
+ /**
+ * Base class for derived classes to implement to provide {@link TvInputManager.Session}.
+ */
+ public abstract class TvInputSessionImpl implements KeyEvent.Callback {
+ private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
+ private final WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowParams;
+ private Surface mSurface;
+ private View mOverlayView;
+ private boolean mOverlayViewEnabled;
+ private IBinder mWindowToken;
+ private Rect mOverlayFrame;
+ private ITvInputSessionCallback mSessionCallback;
+
+ public TvInputSessionImpl() {
+ mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ }
+
+ /**
+ * Enables or disables the overlay view. By default, the overlay view is disabled. Must be
+ * called explicitly after the session is created to enable the overlay view.
+ *
+ * @param enable {@code true} if you want to enable the overlay view. {@code false}
+ * otherwise.
+ */
+ public void setOverlayViewEnabled(final boolean enable) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (enable == mOverlayViewEnabled) {
+ return;
+ }
+ mOverlayViewEnabled = enable;
+ if (enable) {
+ if (mWindowToken != null) {
+ createOverlayView(mWindowToken, mOverlayFrame);
+ }
+ } else {
+ removeOverlayView(false);
+ }
+ }
+ });
+ }
+
+ /**
+ * Dispatches an event to the application using this session.
+ *
+ * @param eventType The type of the event.
+ * @param eventArgs Optional arguments of the event.
+ * @hide
+ */
+ public void dispatchSessionEvent(final String eventType, final Bundle eventArgs) {
+ if (eventType == null) {
+ throw new IllegalArgumentException("eventType should not be null.");
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "dispatchSessionEvent(" + eventType + ")");
+ mSessionCallback.onSessionEvent(eventType, eventArgs);
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in sending event (event=" + eventType + ")");
+ }
+ }
+ });
+ }
+
+ /**
+ * Sends the change on the format of the video stream. This is expected to be called at the
+ * beginning of the playback and later when the format has been changed.
+ *
+ * @param width The width of the video.
+ * @param height The height of the video.
+ * @param interlaced Whether the video is interlaced mode or planer mode.
+ * @hide
+ */
+ public void dispatchVideoStreamChanged(final int width, final int height,
+ final boolean interlaced) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "dispatchVideoSizeChanged");
+ mSessionCallback.onVideoStreamChanged(width, height, interlaced);
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in dispatchVideoSizeChanged");
+ }
+ }
+ });
+ }
+
+ /**
+ * Sends the change on the format of the audio stream. This is expected to be called at the
+ * beginning of the playback and later when the format has been changed.
+ *
+ * @param channelNumber The number of channels in the audio stream.
+ * @hide
+ */
+ public void dispatchAudioStreamChanged(final int channelNumber) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "dispatchAudioStreamChanged");
+ mSessionCallback.onAudioStreamChanged(channelNumber);
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in dispatchAudioStreamChanged");
+ }
+ }
+ });
+ }
+
+ /**
+ * Sends the change on the closed caption stream. This is expected to be called at the
+ * beginning of the playback and later when the stream has been changed.
+ *
+ * @param hasClosedCaption Whether the stream has closed caption or not.
+ * @hide
+ */
+ public void dispatchClosedCaptionStreamChanged(final boolean hasClosedCaption) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) Log.d(TAG, "dispatchClosedCaptionStreamChanged");
+ mSessionCallback.onClosedCaptionStreamChanged(hasClosedCaption);
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in dispatchClosedCaptionStreamChanged");
+ }
+ }
+ });
+ }
+
+ /**
+ * Called when the session is released.
+ */
+ public abstract void onRelease();
+
+ /**
+ * Sets the {@link Surface} for the current input session on which the TV input renders
+ * video.
+ *
+ * @param surface {@link Surface} an application passes to this TV input session.
+ * @return {@code true} if the surface was set, {@code false} otherwise.
+ */
+ public abstract boolean onSetSurface(Surface surface);
+
+ /**
+ * Sets the relative volume of the current TV input session to handle the change of audio
+ * focus by setting.
+ *
+ * @param volume Volume scale from 0.0 to 1.0.
+ */
+ public abstract void onSetVolume(float volume);
+
+ /**
+ * Tunes to a given channel.
+ *
+ * @param channelUri The URI of the channel.
+ * @return {@code true} the tuning was successful, {@code false} otherwise.
+ */
+ public abstract boolean onTune(Uri channelUri);
+
+ /**
+ * Called when an application requests to create an overlay view. Each session
+ * implementation can override this method and return its own view.
+ *
+ * @return a view attached to the overlay window
+ */
+ public View onCreateOverlayView() {
+ return null;
+ }
+
+ /**
+ * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent)
+ * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept key down events before they are processed by the application.
+ * If you return true, the application will not process the event itself. If you return
+ * false, the normal application processing will occur as if the TV input had not seen the
+ * event at all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Default implementation of
+ * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+ * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept key long press events before they are processed by the
+ * application. If you return true, the application will not process the event itself. If
+ * you return false, the normal application processing will occur as if the TV input had not
+ * seen the event at all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Default implementation of
+ * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+ * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept special key multiple events before they are processed by the
+ * application. If you return true, the application will not itself process the event. If
+ * you return false, the normal application processing will occur as if the TV input had not
+ * seen the event at all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param count The number of times the action was made.
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent)
+ * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event).
+ * <p>
+ * Override this to intercept key up events before they are processed by the application. If
+ * you return true, the application will not itself process the event. If you return false,
+ * the normal application processing will occur as if the TV input had not seen the event at
+ * all.
+ *
+ * @param keyCode The value in event.getKeyCode().
+ * @param event Description of the key event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle touch screen motion events on the current input session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onTouchEvent
+ */
+ public boolean onTouchEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle trackball events on the current input session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onTrackballEvent
+ */
+ public boolean onTrackballEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle generic motion events on the current input session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onGenericMotionEvent
+ */
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * This method is called when the application would like to stop using the current input
+ * session.
+ */
+ void release() {
+ onRelease();
+ if (mSurface != null) {
+ mSurface.release();
+ mSurface = null;
+ }
+ removeOverlayView(true);
+ }
+
+ /**
+ * Calls {@link #onSetSurface}.
+ */
+ void setSurface(Surface surface) {
+ onSetSurface(surface);
+ if (mSurface != null) {
+ mSurface.release();
+ }
+ mSurface = surface;
+ // TODO: Handle failure.
+ }
+
+ /**
+ * Calls {@link #onSetVolume}.
+ */
+ void setVolume(float volume) {
+ onSetVolume(volume);
+ }
+
+ /**
+ * Calls {@link #onTune}.
+ */
+ void tune(Uri channelUri) {
+ onTune(channelUri);
+ // TODO: Handle failure.
+ }
+
+ /**
+ * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
+ * to the overlay window.
+ *
+ * @param windowToken A window token of an application.
+ * @param frame A position of the overlay view.
+ */
+ void createOverlayView(IBinder windowToken, Rect frame) {
+ if (mOverlayView != null) {
+ mWindowManager.removeView(mOverlayView);
+ mOverlayView = null;
+ }
+ if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
+ mWindowToken = windowToken;
+ mOverlayFrame = frame;
+ if (!mOverlayViewEnabled) {
+ return;
+ }
+ mOverlayView = onCreateOverlayView();
+ if (mOverlayView == null) {
+ return;
+ }
+ // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
+ // an overlay window above the media window but below the application window.
+ int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+ // We make the overlay view non-focusable and non-touchable so that
+ // the application that owns the window token can decide whether to consume or
+ // dispatch the input events.
+ int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ mWindowParams = new WindowManager.LayoutParams(
+ frame.right - frame.left, frame.bottom - frame.top,
+ frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT);
+ mWindowParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mWindowParams.gravity = Gravity.START | Gravity.TOP;
+ mWindowParams.token = windowToken;
+ mWindowManager.addView(mOverlayView, mWindowParams);
+ }
+
+ /**
+ * Relayouts the current overlay view.
+ *
+ * @param frame A new position of the overlay view.
+ */
+ void relayoutOverlayView(Rect frame) {
+ if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")");
+ mOverlayFrame = frame;
+ if (!mOverlayViewEnabled || mOverlayView == null) {
+ return;
+ }
+ mWindowParams.x = frame.left;
+ mWindowParams.y = frame.top;
+ mWindowParams.width = frame.right - frame.left;
+ mWindowParams.height = frame.bottom - frame.top;
+ mWindowManager.updateViewLayout(mOverlayView, mWindowParams);
+ }
+
+ /**
+ * Removes the current overlay view.
+ */
+ void removeOverlayView(boolean clearWindowToken) {
+ if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayView + ")");
+ if (clearWindowToken) {
+ mWindowToken = null;
+ mOverlayFrame = null;
+ }
+ if (mOverlayView != null) {
+ mWindowManager.removeView(mOverlayView);
+ mOverlayView = null;
+ mWindowParams = null;
+ }
+ }
+
+ /**
+ * Takes care of dispatching incoming input events and tells whether the event was handled.
+ */
+ int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+ if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
+ if (event instanceof KeyEvent) {
+ if (((KeyEvent) event).dispatch(this, mDispatcherState, this)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ } else if (event instanceof MotionEvent) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ final int source = motionEvent.getSource();
+ if (motionEvent.isTouchEvent()) {
+ if (onTouchEvent(motionEvent)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ if (onTrackballEvent(motionEvent)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ } else {
+ if (onGenericMotionEvent(motionEvent)) {
+ return Session.DISPATCH_HANDLED;
+ }
+ }
+ }
+ if (mOverlayView == null || !mOverlayView.isAttachedToWindow()) {
+ return Session.DISPATCH_NOT_HANDLED;
+ }
+ if (!mOverlayView.hasWindowFocus()) {
+ mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
+ }
+ mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
+ return Session.DISPATCH_IN_PROGRESS;
+ }
+
+ private void setSessionCallback(ITvInputSessionCallback callback) {
+ mSessionCallback = callback;
+ }
+ }
+
+ private final class ServiceHandler extends Handler {
+ private static final int DO_CREATE_SESSION = 1;
+ private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
+
+ @Override
+ public final void handleMessage(Message msg) {
+ switch (msg.what) {
+ case DO_CREATE_SESSION: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ InputChannel channel = (InputChannel) args.arg1;
+ ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
+ try {
+ TvInputSessionImpl sessionImpl = onCreateSession();
+ if (sessionImpl == null) {
+ // Failed to create a session.
+ cb.onSessionCreated(null);
+ } else {
+ sessionImpl.setSessionCallback(cb);
+ ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
+ sessionImpl, channel);
+ cb.onSessionCreated(stub);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated");
+ }
+ args.recycle();
+ return;
+ }
+ case DO_BROADCAST_AVAILABILITY_CHANGE: {
+ boolean isAvailable = (Boolean) msg.obj;
+ int n = mCallbacks.beginBroadcast();
+ try {
+ for (int i = 0; i < n; i++) {
+ mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mId, isAvailable);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unexpected exception", e);
+ } finally {
+ mCallbacks.finishBroadcast();
+ }
+ return;
+ }
+ default: {
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/tv/TvStreamConfig.aidl b/media/java/android/media/tv/TvStreamConfig.aidl
new file mode 100644
index 0000000..569fcc0
--- /dev/null
+++ b/media/java/android/media/tv/TvStreamConfig.aidl
@@ -0,0 +1,20 @@
+/*
+ *
+ * Copyright 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.media.tv;
+
+parcelable TvStreamConfig; \ No newline at end of file
diff --git a/media/java/android/media/tv/TvStreamConfig.java b/media/java/android/media/tv/TvStreamConfig.java
new file mode 100644
index 0000000..7f0c92f
--- /dev/null
+++ b/media/java/android/media/tv/TvStreamConfig.java
@@ -0,0 +1,157 @@
+/*
+ * 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.media.tv;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class TvStreamConfig implements Parcelable {
+ static final String TAG = TvStreamConfig.class.getSimpleName();
+
+ public final static int STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE = 1;
+ public final static int STREAM_TYPE_BUFFER_PRODUCER = 2;
+
+ private int mStreamId;
+ private int mType;
+ // TODO: Revisit if max widht/height really make sense.
+ private int mMaxWidth;
+ private int mMaxHeight;
+ /**
+ * Generations are incremented once framework receives STREAM_CONFIGURATION_CHANGED event from
+ * HAL module. Framework should throw away outdated configurations and get new configurations
+ * via tv_input_device::get_stream_configurations().
+ */
+ private int mGeneration;
+
+ public static final Parcelable.Creator<TvStreamConfig> CREATOR =
+ new Parcelable.Creator<TvStreamConfig>() {
+ @Override
+ public TvStreamConfig createFromParcel(Parcel source) {
+ try {
+ return new Builder().
+ streamId(source.readInt()).
+ type(source.readInt()).
+ maxWidth(source.readInt()).
+ maxHeight(source.readInt()).
+ generation(source.readInt()).build();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating TvStreamConfig from parcel", e);
+ return null;
+ }
+ }
+
+ @Override
+ public TvStreamConfig[] newArray(int size) {
+ return new TvStreamConfig[size];
+ }
+ };
+
+ private TvStreamConfig() {}
+
+ public int getStreamId() {
+ return mStreamId;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ public int getMaxWidth() {
+ return mMaxWidth;
+ }
+
+ public int getMaxHeight() {
+ return mMaxHeight;
+ }
+
+ public int getGeneration() {
+ return mGeneration;
+ }
+
+ // Parcelable
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mStreamId);
+ dest.writeInt(mType);
+ dest.writeInt(mMaxWidth);
+ dest.writeInt(mMaxHeight);
+ dest.writeInt(mGeneration);
+ }
+
+ /**
+ * A helper class for creating a TvStreamConfig object.
+ */
+ public static final class Builder {
+ private Integer mStreamId;
+ private Integer mType;
+ private Integer mMaxWidth;
+ private Integer mMaxHeight;
+ private Integer mGeneration;
+
+ public Builder() {
+ }
+
+ public Builder streamId(int streamId) {
+ mStreamId = streamId;
+ return this;
+ }
+
+ public Builder type(int type) {
+ mType = type;
+ return this;
+ }
+
+ public Builder maxWidth(int maxWidth) {
+ mMaxWidth = maxWidth;
+ return this;
+ }
+
+ public Builder maxHeight(int maxHeight) {
+ mMaxHeight = maxHeight;
+ return this;
+ }
+
+ public Builder generation(int generation) {
+ mGeneration = generation;
+ return this;
+ }
+
+ public TvStreamConfig build() {
+ if (mStreamId == null || mType == null || mMaxWidth == null || mMaxHeight == null
+ || mGeneration == null) {
+ throw new UnsupportedOperationException();
+ }
+
+ TvStreamConfig config = new TvStreamConfig();
+ config.mStreamId = mStreamId;
+ config.mType = mType;
+ config.mMaxWidth = mMaxWidth;
+ config.mMaxHeight = mMaxHeight;
+ config.mGeneration = mGeneration;
+ return config;
+ }
+ }
+} \ No newline at end of file
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
new file mode 100644
index 0000000..d8b362d
--- /dev/null
+++ b/media/java/android/media/tv/TvView.java
@@ -0,0 +1,423 @@
+/*
+ * 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.media.tv;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.media.tv.TvInputManager.Session;
+import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
+import android.media.tv.TvInputManager.SessionCallback;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewRootImpl;
+
+/**
+ * View playing TV
+ */
+public class TvView extends SurfaceView {
+ // STOPSHIP: Turn debugging off.
+ private static final boolean DEBUG = true;
+ private static final String TAG = "TvView";
+
+ private final Handler mHandler = new Handler();
+ private TvInputManager.Session mSession;
+ private Surface mSurface;
+ private boolean mOverlayViewCreated;
+ private Rect mOverlayViewFrame;
+ private final TvInputManager mTvInputManager;
+ private SessionCallback mSessionCallback;
+ private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
+
+ private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" + width
+ + ", height=" + height + ")");
+ if (holder.getSurface() == mSurface) {
+ return;
+ }
+ mSurface = holder.getSurface();
+ setSessionSurface(mSurface);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mSurface = holder.getSurface();
+ setSessionSurface(mSurface);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mSurface = null;
+ setSessionSurface(null);
+ }
+ };
+
+ private final FinishedInputEventCallback mFinishedInputEventCallback =
+ new FinishedInputEventCallback() {
+ @Override
+ public void onFinishedInputEvent(Object token, boolean handled) {
+ if (DEBUG) {
+ Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + handled + ")");
+ }
+ if (handled) {
+ return;
+ }
+ // TODO: Re-order unhandled events.
+ InputEvent event = (InputEvent) token;
+ if (dispatchUnhandledInputEvent(event)) {
+ return;
+ }
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.dispatchUnhandledInputEvent(event);
+ }
+ }
+ };
+
+ public TvView(Context context) {
+ this(context, null, 0);
+ }
+
+ public TvView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TvView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ getHolder().addCallback(mSurfaceHolderCallback);
+ mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
+ }
+
+ /**
+ * Binds a TV input to this view. {@link SessionCallback#onSessionCreated} will be
+ * called to send the result of this binding with {@link TvInputManager.Session}.
+ * If a TV input is already bound, the input will be unbound from this view and its session
+ * will be released.
+ *
+ * @param inputId the id of TV input which will be bound to this view.
+ * @param callback called when TV input is bound. The callback sends
+ * {@link TvInputManager.Session}
+ * @throws IllegalArgumentException if any of the arguments is {@code null}.
+ */
+ public void bindTvInput(String inputId, SessionCallback callback) {
+ if (TextUtils.isEmpty(inputId)) {
+ throw new IllegalArgumentException("inputId cannot be null or an empty string");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ if (mSession != null) {
+ release();
+ }
+ // When bindTvInput is called multiple times before the callback is called,
+ // only the callback of the last bindTvInput call will be actually called back.
+ // The previous callbacks will be ignored. For the logic, mSessionCallback
+ // is newly assigned for every bindTvInput call and compared with
+ // MySessionCreateCallback.this.
+ mSessionCallback = new MySessionCallback(callback);
+ mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
+ }
+
+ /**
+ * Unbinds a TV input currently bound. Its corresponding {@link TvInputManager.Session}
+ * is released.
+ */
+ public void unbindTvInput() {
+ if (mSession != null) {
+ release();
+ }
+ mSessionCallback = null;
+ }
+
+ /**
+ * Dispatches an unhandled input event to the next receiver.
+ * <p>
+ * Except system keys, TvView always consumes input events in the normal flow. This is called
+ * asynchronously from where the event is dispatched. It gives the host application a chance to
+ * dispatch the unhandled input events.
+ *
+ * @param event The input event.
+ * @return {@code true} if the event was handled by the view, {@code false} otherwise.
+ */
+ public boolean dispatchUnhandledInputEvent(InputEvent event) {
+ if (mOnUnhandledInputEventListener != null) {
+ if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
+ return true;
+ }
+ }
+ return onUnhandledInputEvent(event);
+ }
+
+ /**
+ * Called when an unhandled input event was also not handled by the user provided callback. This
+ * is the last chance to handle the unhandled input event in the TvView.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to be
+ * handled by the next receiver, return {@code false}.
+ */
+ public boolean onUnhandledInputEvent(InputEvent event) {
+ return false;
+ }
+
+ /**
+ * Registers a callback to be invoked when an input event was not handled by the bound TV input.
+ *
+ * @param listener The callback to invoke when the unhandled input event was received.
+ */
+ public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) {
+ mOnUnhandledInputEventListener = listener;
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ InputEvent copiedEvent = event.copy();
+ int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
+ mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent event) {
+ if (super.dispatchTouchEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchTouchEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ InputEvent copiedEvent = event.copy();
+ int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
+ mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent event) {
+ if (super.dispatchTrackballEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchTrackballEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ InputEvent copiedEvent = event.copy();
+ int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
+ mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent event) {
+ if (super.dispatchGenericMotionEvent(event)) {
+ return true;
+ }
+ if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent(" + event + ")");
+ if (mSession == null) {
+ return false;
+ }
+ InputEvent copiedEvent = event.copy();
+ int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
+ mHandler);
+ return ret != Session.DISPATCH_NOT_HANDLED;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ createSessionOverlayView();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ removeSessionOverlayView();
+ super.onDetachedFromWindow();
+ }
+
+ /** @hide */
+ @Override
+ protected void updateWindow(boolean force, boolean redrawNeeded) {
+ super.updateWindow(force, redrawNeeded);
+ relayoutSessionOverlayView();
+ }
+
+ private void release() {
+ setSessionSurface(null);
+ removeSessionOverlayView();
+ mSession.release();
+ mSession = null;
+ }
+
+ private void setSessionSurface(Surface surface) {
+ if (mSession == null) {
+ return;
+ }
+ mSession.setSurface(surface);
+ }
+
+ private void createSessionOverlayView() {
+ if (mSession == null || !isAttachedToWindow()
+ || mOverlayViewCreated) {
+ return;
+ }
+ mOverlayViewFrame = getViewFrameOnScreen();
+ mSession.createOverlayView(this, mOverlayViewFrame);
+ mOverlayViewCreated = true;
+ }
+
+ private void removeSessionOverlayView() {
+ if (mSession == null || !mOverlayViewCreated) {
+ return;
+ }
+ mSession.removeOverlayView();
+ mOverlayViewCreated = false;
+ mOverlayViewFrame = null;
+ }
+
+ private void relayoutSessionOverlayView() {
+ if (mSession == null || !isAttachedToWindow()
+ || !mOverlayViewCreated) {
+ return;
+ }
+ Rect viewFrame = getViewFrameOnScreen();
+ if (viewFrame.equals(mOverlayViewFrame)) {
+ return;
+ }
+ mSession.relayoutOverlayView(viewFrame);
+ mOverlayViewFrame = viewFrame;
+ }
+
+ private Rect getViewFrameOnScreen() {
+ int[] location = new int[2];
+ getLocationOnScreen(location);
+ return new Rect(location[0], location[1],
+ location[0] + getWidth(), location[1] + getHeight());
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when the unhandled input event is received.
+ */
+ public interface OnUnhandledInputEventListener {
+ /**
+ * Called when an input event was not handled by the bound TV input.
+ * <p>
+ * This is called asynchronously from where the event is dispatched. It gives the host
+ * application a chance to handle the unhandled input events.
+ *
+ * @param event The input event.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ */
+ boolean onUnhandledInputEvent(InputEvent event);
+ }
+
+ private class MySessionCallback extends SessionCallback {
+ final SessionCallback mExternalCallback;
+
+ MySessionCallback(SessionCallback externalCallback) {
+ mExternalCallback = externalCallback;
+ }
+
+ @Override
+ public void onSessionCreated(Session session) {
+ if (this != mSessionCallback) {
+ // This callback is obsolete.
+ if (session != null) {
+ session.release();
+ }
+ return;
+ }
+ mSession = session;
+ if (session != null) {
+ // mSurface may not be ready yet as soon as starting an application.
+ // In the case, we don't send Session.setSurface(null) unnecessarily.
+ // setSessionSurface will be called in surfaceCreated.
+ if (mSurface != null) {
+ setSessionSurface(mSurface);
+ }
+ createSessionOverlayView();
+ }
+ if (mExternalCallback != null) {
+ mExternalCallback.onSessionCreated(session);
+ }
+ }
+
+ @Override
+ public void onSessionReleased(Session session) {
+ mSession = null;
+ if (mExternalCallback != null) {
+ mExternalCallback.onSessionReleased(session);
+ }
+ }
+
+ @Override
+ public void onVideoStreamChanged(Session session, int width, int height,
+ boolean interlaced) {
+ if (DEBUG) {
+ Log.d(TAG, "onVideoSizeChanged(" + width + ", " + height + ")");
+ }
+ if (mExternalCallback != null) {
+ mExternalCallback.onVideoStreamChanged(session, width, height, interlaced);
+ }
+ }
+
+ @Override
+ public void onAudioStreamChanged(Session session, int channelCount) {
+ if (DEBUG) {
+ Log.d(TAG, "onAudioStreamChanged(" + channelCount + ")");
+ }
+ if (mExternalCallback != null) {
+ mExternalCallback.onAudioStreamChanged(session, channelCount);
+ }
+ }
+
+ @Override
+ public void onClosedCaptionStreamChanged(Session session, boolean hasClosedCaption) {
+ if (DEBUG) {
+ Log.d(TAG, "onClosedCaptionStreamChanged(" + hasClosedCaption + ")");
+ }
+ if (mExternalCallback != null) {
+ mExternalCallback.onClosedCaptionStreamChanged(session, hasClosedCaption);
+ }
+ }
+
+ @Override
+ public void onSessionEvent(TvInputManager.Session session, String eventType,
+ Bundle eventArgs) {
+ if (mExternalCallback != null) {
+ mExternalCallback.onSessionEvent(session, eventType, eventArgs);
+ }
+ }
+ }
+}