summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJae Seo <jaeseo@google.com>2014-02-20 18:23:25 -0800
committerJae Seo <jaeseo@google.com>2014-04-08 13:35:21 -0700
commit3957091ba8f08c02b5e781098cb955a5f697a1ff (patch)
treec8739c677c87e62ea6c1e8bc45de027d1e65d87c
parent53c2cf799fddfae7f6fc9ca1840ea345308b79ee (diff)
Initial round of Television Input Framework
This provides APIs to control and create individual television inputs on the system which will later be hosted by television applications. Change-Id: I6866d28e78175a1bff2c32a85c5d77e94d0cd60c
-rw-r--r--Android.mk6
-rw-r--r--CleanSpec.mk1
-rw-r--r--api/current.txt56
-rw-r--r--core/java/android/app/ContextImpl.java9
-rw-r--r--core/java/android/content/Context.java10
-rw-r--r--core/java/android/tv/ITvInputClient.aidl30
-rw-r--r--core/java/android/tv/ITvInputManager.aidl43
-rw-r--r--core/java/android/tv/ITvInputService.aidl31
-rw-r--r--core/java/android/tv/ITvInputServiceCallback.aidl28
-rw-r--r--core/java/android/tv/ITvInputSession.aidl34
-rw-r--r--core/java/android/tv/ITvInputSessionCallback.aidl28
-rw-r--r--core/java/android/tv/ITvInputSessionWrapper.java99
-rw-r--r--core/java/android/tv/TvInputInfo.aidl19
-rw-r--r--core/java/android/tv/TvInputInfo.java124
-rw-r--r--core/java/android/tv/TvInputManager.java392
-rw-r--r--core/java/android/tv/TvInputService.java254
-rw-r--r--core/java/android/tv/TvInputSession.java57
-rw-r--r--core/res/AndroidManifest.xml7
-rw-r--r--core/res/res/values/strings.xml6
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java667
-rw-r--r--services/java/com/android/server/SystemServer.java9
21 files changed, 1910 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
index e1c15470c648..9cd04eb50477 100644
--- a/Android.mk
+++ b/Android.mk
@@ -197,6 +197,12 @@ LOCAL_SRC_FILES += \
core/java/android/service/wallpaper/IWallpaperConnection.aidl \
core/java/android/service/wallpaper/IWallpaperEngine.aidl \
core/java/android/service/wallpaper/IWallpaperService.aidl \
+ core/java/android/tv/ITvInputClient.aidl \
+ core/java/android/tv/ITvInputManager.aidl \
+ core/java/android/tv/ITvInputService.aidl \
+ core/java/android/tv/ITvInputServiceCallback.aidl \
+ core/java/android/tv/ITvInputSession.aidl \
+ core/java/android/tv/ITvInputSessionCallback.aidl \
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
core/java/android/view/accessibility/IAccessibilityManager.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 4f0e603331f4..ffdac4e518ff 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -188,6 +188,7 @@ $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framew
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/services_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/media/java/android/media/IMedia*)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/view/IMagnificationCallbacks*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/tv/)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/api/current.txt b/api/current.txt
index 1dcef2942ab2..747b6f20a33c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -29,6 +29,7 @@ package android {
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
field public static final java.lang.String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
field public static final java.lang.String BIND_TRUST_AGENT_SERVICE = "android.permission.BIND_TRUST_AGENT_SERVICE";
+ field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
field public static final java.lang.String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER";
field public static final java.lang.String BLUETOOTH = "android.permission.BLUETOOTH";
@@ -6497,6 +6498,7 @@ package android.content {
field public static final java.lang.String STORAGE_SERVICE = "storage";
field public static final java.lang.String TELEPHONY_SERVICE = "phone";
field public static final java.lang.String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
+ field public static final java.lang.String TV_INPUT_SERVICE = "tv_input";
field public static final java.lang.String UI_MODE_SERVICE = "uimode";
field public static final java.lang.String USB_SERVICE = "usb";
field public static final java.lang.String USER_SERVICE = "user";
@@ -27614,6 +27616,60 @@ package android.transition {
}
+package android.tv {
+
+ public final class TvInputInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public android.content.ComponentName getComponent();
+ method public java.lang.String getId();
+ method public java.lang.String getPackageName();
+ method public java.lang.String getServiceName();
+ method public java.lang.CharSequence loadLabel(android.content.pm.PackageManager);
+ method public void writeToParcel(android.os.Parcel, int);
+ }
+
+ public final class TvInputManager {
+ method public void createSession(android.content.ComponentName, android.tv.TvInputManager.SessionCreateCallback, android.os.Handler);
+ method public boolean getAvailability(android.content.ComponentName);
+ method public java.util.List<android.tv.TvInputInfo> getTvInputList();
+ method public void registerListener(android.content.ComponentName, android.tv.TvInputManager.TvInputListener, android.os.Handler);
+ method public void unregisterListener(android.content.ComponentName, android.tv.TvInputManager.TvInputListener);
+ }
+
+ public static final class TvInputManager.Session {
+ method public void release();
+ method public void setSurface(android.view.Surface);
+ method public void setVolume(float);
+ method public void tune(android.net.Uri);
+ }
+
+ public static abstract interface TvInputManager.SessionCreateCallback {
+ method public abstract void onSessionCreated(android.tv.TvInputManager.Session);
+ }
+
+ public static abstract class TvInputManager.TvInputListener {
+ ctor public TvInputManager.TvInputListener();
+ method public void onAvailabilityChanged(android.content.ComponentName, boolean);
+ }
+
+ public abstract class TvInputService extends android.app.Service {
+ ctor public TvInputService();
+ method public final android.os.IBinder onBind(android.content.Intent);
+ method public abstract android.tv.TvInputService.TvInputSessionImpl onCreateSession();
+ method public final void setAvailable(boolean);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.tv.TvInputService";
+ }
+
+ public static abstract class TvInputService.TvInputSessionImpl {
+ ctor public TvInputService.TvInputSessionImpl();
+ method public abstract void onRelease();
+ method public abstract boolean onSetSurface(android.view.Surface);
+ method public abstract void onSetVolume(float);
+ method public abstract boolean onTune(android.net.Uri);
+ }
+
+}
+
package android.util {
public class AndroidException extends java.lang.Exception {
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 7149ab9d989e..e3b8d5cddf3a 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -103,6 +103,8 @@ import android.os.storage.StorageManager;
import android.print.IPrintManager;
import android.print.PrintManager;
import android.telephony.TelephonyManager;
+import android.tv.ITvInputManager;
+import android.tv.TvInputManager;
import android.content.ClipboardManager;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
@@ -629,6 +631,13 @@ class ContextImpl extends Context {
return new TrustManager(b);
}
});
+
+ registerService(TV_INPUT_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder iBinder = ServiceManager.getService(TV_INPUT_SERVICE);
+ ITvInputManager service = ITvInputManager.Stub.asInterface(iBinder);
+ return new TvInputManager(service, UserHandle.myUserId());
+ }});
}
static ContextImpl getImpl(Context context) {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 27e526b2a31d..17b57bb7f980 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2609,6 +2609,16 @@ public abstract class Context {
public static final String TRUST_SERVICE = "trust";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.tv.TvInputManager} for interacting with TV inputs on the
+ * device.
+ *
+ * @see #getSystemService
+ * @see android.tv.TvInputManager
+ */
+ public static final String TV_INPUT_SERVICE = "tv_input";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/tv/ITvInputClient.aidl b/core/java/android/tv/ITvInputClient.aidl
new file mode 100644
index 000000000000..43be6f008fd2
--- /dev/null
+++ b/core/java/android/tv/ITvInputClient.aidl
@@ -0,0 +1,30 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.tv.ITvInputSession;
+
+/**
+ * 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 ComponentName name, IBinder token, int seq);
+ void onAvailabilityChanged(in ComponentName name, boolean isAvailable);
+}
diff --git a/core/java/android/tv/ITvInputManager.aidl b/core/java/android/tv/ITvInputManager.aidl
new file mode 100644
index 000000000000..a927dc9ee695
--- /dev/null
+++ b/core/java/android/tv/ITvInputManager.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.tv.ITvInputClient;
+import android.tv.TvInputInfo;
+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 ComponentName name, int userId);
+
+ void registerCallback(in ITvInputClient client, in ComponentName name, int userId);
+ void unregisterCallback(in ITvInputClient client, in ComponentName name, int userId);
+
+ void createSession(in ITvInputClient client, in ComponentName name, 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);
+}
diff --git a/core/java/android/tv/ITvInputService.aidl b/core/java/android/tv/ITvInputService.aidl
new file mode 100644
index 000000000000..d80f286d0e30
--- /dev/null
+++ b/core/java/android/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.tv;
+
+import android.tv.ITvInputServiceCallback;
+import android.tv.ITvInputSession;
+import android.tv.ITvInputSessionCallback;
+
+/**
+ * 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(ITvInputSessionCallback callback);
+}
diff --git a/core/java/android/tv/ITvInputServiceCallback.aidl b/core/java/android/tv/ITvInputServiceCallback.aidl
new file mode 100644
index 000000000000..e535c81e60bd
--- /dev/null
+++ b/core/java/android/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.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 ComponentName name, boolean isAvailable);
+}
diff --git a/core/java/android/tv/ITvInputSession.aidl b/core/java/android/tv/ITvInputSession.aidl
new file mode 100644
index 000000000000..d379d2d96c28
--- /dev/null
+++ b/core/java/android/tv/ITvInputSession.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.tv;
+
+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);
+}
diff --git a/core/java/android/tv/ITvInputSessionCallback.aidl b/core/java/android/tv/ITvInputSessionCallback.aidl
new file mode 100644
index 000000000000..a2bd0d7a0658
--- /dev/null
+++ b/core/java/android/tv/ITvInputSessionCallback.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.tv;
+
+import android.tv.ITvInputSession;
+
+/**
+ * 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);
+}
diff --git a/core/java/android/tv/ITvInputSessionWrapper.java b/core/java/android/tv/ITvInputSessionWrapper.java
new file mode 100644
index 000000000000..fd4e1e367597
--- /dev/null
+++ b/core/java/android/tv/ITvInputSessionWrapper.java
@@ -0,0 +1,99 @@
+/*
+ * 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.tv;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Message;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.internal.os.HandlerCaller;
+
+/**
+ * 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 TvInputSession mTvInputSession;
+ private final HandlerCaller mCaller;
+
+ public ITvInputSessionWrapper(Context context, TvInputSession session) {
+ mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
+ mTvInputSession = session;
+ }
+
+ @Override
+ public void executeMessage(Message msg) {
+ if (mTvInputSession == null) {
+ return;
+ }
+
+ switch (msg.what) {
+ case DO_RELEASE: {
+ mTvInputSession.release();
+ mTvInputSession = null;
+ return;
+ }
+ case DO_SET_SURFACE: {
+ mTvInputSession.setSurface((Surface) msg.obj);
+ return;
+ }
+ case DO_SET_VOLUME: {
+ mTvInputSession.setVolume((Float) msg.obj);
+ return;
+ }
+ case DO_TUNE: {
+ mTvInputSession.tune((Uri) msg.obj);
+ 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));
+ }
+}
diff --git a/core/java/android/tv/TvInputInfo.aidl b/core/java/android/tv/TvInputInfo.aidl
new file mode 100644
index 000000000000..abc4b47ad183
--- /dev/null
+++ b/core/java/android/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.tv;
+
+parcelable TvInputInfo;
diff --git a/core/java/android/tv/TvInputInfo.java b/core/java/android/tv/TvInputInfo.java
new file mode 100644
index 000000000000..90625d8d33eb
--- /dev/null
+++ b/core/java/android/tv/TvInputInfo.java
@@ -0,0 +1,124 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to specify meta information of a TV input.
+ */
+public final class TvInputInfo implements Parcelable {
+ private final ResolveInfo mService;
+ private final String mId;
+
+ /**
+ * Constructor.
+ *
+ * @param service The ResolveInfo returned from the package manager about this TV input service.
+ * @hide
+ */
+ public TvInputInfo(ResolveInfo service) {
+ mService = service;
+ ServiceInfo si = service.serviceInfo;
+ mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Loads the user-displayed label for this TV input service.
+ *
+ * @param pm Supplies a PackageManager used to load the TV input's resources.
+ * @return Returns 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;
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+}
diff --git a/core/java/android/tv/TvInputManager.java b/core/java/android/tv/TvInputManager.java
new file mode 100644
index 000000000000..0b6ab6427ade
--- /dev/null
+++ b/core/java/android/tv/TvInputManager.java
@@ -0,0 +1,392 @@
+/*
+ * 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.tv;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Surface;
+
+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<ComponentName, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
+ new HashMap<ComponentName, List<TvInputListenerRecord>>();
+
+ // A mapping from the sequence number of a session to its SessionCreateCallbackRecord.
+ private final SparseArray<SessionCreateCallbackRecord> mSessionCreateCallbackRecordMap =
+ new SparseArray<SessionCreateCallbackRecord>();
+
+ // A sequence number for the next session to be created. Should be protected by a lock
+ // {@code mSessionCreateCallbackRecordMap}.
+ private int mNextSeq;
+
+ private final ITvInputClient mClient;
+
+ private final int mUserId;
+
+ /**
+ * Interface used to receive the created session.
+ */
+ public interface SessionCreateCallback {
+ /**
+ * 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.
+ */
+ void onSessionCreated(Session session);
+ }
+
+ private static final class SessionCreateCallbackRecord {
+ private final SessionCreateCallback mSessionCreateCallback;
+ private final Handler mHandler;
+
+ public SessionCreateCallbackRecord(SessionCreateCallback sessionCreateCallback,
+ Handler handler) {
+ mSessionCreateCallback = sessionCreateCallback;
+ mHandler = handler;
+ }
+
+ public void postSessionCreated(final Session session) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCreateCallback.onSessionCreated(session);
+ }
+ });
+ }
+ }
+
+ /**
+ * 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 name {@link ComponentName} of {@link android.app.Service} that implements the
+ * given TV input.
+ * @param isAvailable {@code true} if the given TV input is available to show TV programs.
+ * {@code false} otherwise.
+ */
+ public void onAvailabilityChanged(ComponentName name, 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 ComponentName name, final boolean isAvailable) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onAvailabilityChanged(name, isAvailable);
+ }
+ });
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public TvInputManager(ITvInputManager service, int userId) {
+ mService = service;
+ mUserId = userId;
+ mClient = new ITvInputClient.Stub() {
+ @Override
+ public void onSessionCreated(ComponentName name, IBinder token, int seq) {
+ synchronized (mSessionCreateCallbackRecordMap) {
+ SessionCreateCallbackRecord record = mSessionCreateCallbackRecordMap.get(seq);
+ mSessionCreateCallbackRecordMap.delete(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for " + token);
+ return;
+ }
+ Session session = null;
+ if (token != null) {
+ session = new Session(name, token, mService, mUserId);
+ }
+ record.postSessionCreated(session);
+ }
+ }
+
+ @Override
+ public void onAvailabilityChanged(ComponentName name, boolean isAvailable) {
+ synchronized (mTvInputListenerRecordsMap) {
+ List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
+ 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(name, 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 name {@link ComponentName} of {@link android.app.Service} that implements the given 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(ComponentName name) {
+ if (name == null) {
+ throw new IllegalArgumentException("name cannot be null");
+ }
+ synchronized (mTvInputListenerRecordsMap) {
+ List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
+ if (records == null || records.size() == 0) {
+ throw new IllegalStateException("At least one listener should be registered.");
+ }
+ }
+ try {
+ return mService.getAvailability(mClient, name, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Registers a {@link TvInputListener} for a given TV input.
+ *
+ * @param name {@link ComponentName} of {@link android.app.Service} that implements the given 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(ComponentName name, TvInputListener listener, Handler handler) {
+ if (name == null) {
+ throw new IllegalArgumentException("name 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(name);
+ if (records == null) {
+ records = new ArrayList<TvInputListenerRecord>();
+ mTvInputListenerRecordsMap.put(name, records);
+ try {
+ mService.registerCallback(mClient, name, 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 name {@link ComponentName} of {@link android.app.Service} that implements the given 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(ComponentName name, final TvInputListener listener) {
+ if (name == null) {
+ throw new IllegalArgumentException("name cannot be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener cannot be null");
+ }
+ synchronized (mTvInputListenerRecordsMap) {
+ List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(name);
+ if (records == null) {
+ Log.e(TAG, "No listener found for " + name.getClassName());
+ 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, name, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ } finally {
+ mTvInputListenerRecordsMap.remove(name);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a {@link TvInputSession} interface 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 name {@link ComponentName} of {@link android.app.Service} that implements the given 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(ComponentName name, final SessionCreateCallback callback,
+ Handler handler) {
+ if (name == null) {
+ throw new IllegalArgumentException("name cannot be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback cannot be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler cannot be null");
+ }
+ SessionCreateCallbackRecord record = new SessionCreateCallbackRecord(callback, handler);
+ synchronized (mSessionCreateCallbackRecordMap) {
+ int seq = mNextSeq++;
+ mSessionCreateCallbackRecordMap.put(seq, record);
+ try {
+ mService.createSession(mClient, name, seq, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ /** The Session provides the per-session functionality of TV inputs. */
+ public static final class Session {
+ private final ITvInputManager mService;
+ private final IBinder mToken;
+ private final int mUserId;
+
+ /** @hide */
+ private Session(ComponentName name, IBinder token, ITvInputManager service, int userId) {
+ mToken = token;
+ mService = service;
+ mUserId = userId;
+ }
+
+ /**
+ * Releases this session.
+ */
+ public void release() {
+ try {
+ mService.releaseSession(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Sets the {@link android.view.Surface} for this session.
+ *
+ * @param surface A {@link android.view.Surface} used to render video.
+ */
+ public void setSurface(Surface surface) {
+ // 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.
+ */
+ public void setVolume(float volume) {
+ 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}.
+ */
+ public void tune(Uri channelUri) {
+ if (channelUri == null) {
+ throw new IllegalArgumentException("channelUri cannot be null");
+ }
+ try {
+ mService.tune(mToken, channelUri, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/core/java/android/tv/TvInputService.java b/core/java/android/tv/TvInputService.java
new file mode 100644
index 000000000000..e43cc950bd7f
--- /dev/null
+++ b/core/java/android/tv/TvInputService.java
@@ -0,0 +1,254 @@
+/*
+ * 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.tv;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+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.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * 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.tv.TvInputService";
+
+ private ComponentName mComponentName;
+ private final Handler mHandler = new ServiceHandler();
+ private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
+ new RemoteCallbackList<ITvInputServiceCallback>();
+ private boolean mAvailable;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mComponentName = 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(mComponentName, 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(ITvInputSessionCallback cb) {
+ if (cb != null) {
+ mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, cb).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 TvInputSession}.
+ */
+ public abstract static class TvInputSessionImpl {
+ /**
+ * 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);
+ }
+
+ /**
+ * Internal implementation of {@link TvInputSession}. This takes care of basic maintenance of
+ * the TV input session but most behavior must be implemented in {@link TvInputSessionImpl}
+ * returned by {@link TvInputService#onCreateSession}.
+ */
+ private static class TvInputSessionImplInternal extends TvInputSession {
+ private final TvInputSessionImpl mSessionImpl;
+
+ public TvInputSessionImplInternal(TvInputSessionImpl sessionImpl) {
+ mSessionImpl = sessionImpl;
+ }
+
+ /**
+ * This method is called when the application would like to stop using the current input
+ * session.
+ */
+ @Override
+ public final void release() {
+ mSessionImpl.onRelease();
+ }
+
+ /**
+ * Calls {@link TvInputSessionImpl#onSetSurface}.
+ */
+ @Override
+ public final void setSurface(Surface surface) {
+ mSessionImpl.onSetSurface(surface);
+ // TODO: Handle failure.
+ }
+
+ /**
+ * Calls {@link TvInputSessionImpl#onSetVolume}.
+ */
+ @Override
+ public final void setVolume(float volume) {
+ mSessionImpl.onSetVolume(volume);
+ }
+
+ /**
+ * Calls {@link TvInputSessionImpl#onTune}.
+ */
+ @Override
+ public final void tune(Uri channelUri) {
+ mSessionImpl.onTune(channelUri);
+ // TODO: Handle failure.
+ }
+ }
+
+ 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: {
+ ITvInputSessionCallback cb = (ITvInputSessionCallback) msg.obj;
+ try {
+ TvInputSessionImpl sessionImpl = onCreateSession();
+ if (sessionImpl == null) {
+ // Failed to create a session.
+ cb.onSessionCreated(null);
+ return;
+ }
+ ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
+ new TvInputSessionImplInternal(sessionImpl));
+ cb.onSessionCreated(stub);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated");
+ }
+ 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(mComponentName,
+ 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/core/java/android/tv/TvInputSession.java b/core/java/android/tv/TvInputSession.java
new file mode 100644
index 000000000000..5b70a0b37178
--- /dev/null
+++ b/core/java/android/tv/TvInputSession.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tv;
+
+import android.net.Uri;
+import android.view.Surface;
+
+/**
+ * The TvInputSession provides the per-session functionality of TvInputService.
+ *
+ * @hide
+ */
+public abstract class TvInputSession {
+ /**
+ * This method is called when the application would like to stop using the current input
+ * session.
+ */
+ public void release() { }
+
+ /**
+ * Sets the {@link Surface} for the current input session on which the TV input renders video.
+ *
+ * @param surface {@link Surface} to be used for the video playback of this session.
+ */
+ public void setSurface(Surface surface) { }
+
+ /**
+ * This method is called when the application needs to handle the change of audio focus by
+ * setting the relative volume of the current TV input service session.
+ *
+ * @param volume Volume scale from 0.0 to 1.0.
+ */
+ // 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.
+ public void setVolume(float volume) { }
+
+ /**
+ * Tunes to a given channel.
+ *
+ * @param channelUri The URI of the channel.
+ */
+ public void tune(Uri channelUri) { }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3857cd1bf1b4..0f772f184b37 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2036,6 +2036,13 @@
android:description="@string/permdesc_bindRemoteDisplay"
android:protectionLevel="signature" />
+ <!-- Must be required by a {@link android.tv.TvInputService}
+ to ensure that only the system can bind to it. -->
+ <permission android:name="android.permission.BIND_TV_INPUT"
+ android:label="@string/permlab_bindTvInput"
+ android:description="@string/permdesc_bindTvInput"
+ android:protectionLevel="signature|system" />
+
<!-- Must be required by device administration receiver, to ensure that only the
system can interact with it. -->
<permission android:name="android.permission.BIND_DEVICE_ADMIN"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 902aea81dc13..4a121d15b296 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1056,6 +1056,12 @@
a device administrator. Should never be needed for normal apps.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_bindTvInput">bind to a TV input</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_bindTvInput">Allows the holder to bind to the top-level
+ interface of a TV input. Should never be needed for normal apps.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_manageDeviceAdmins">add or remove a device admin</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_manageDeviceAdmins">Allows the holder to add or remove active device
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
new file mode 100644
index 000000000000..972b0883c727
--- /dev/null
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -0,0 +1,667 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.tv;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.tv.ITvInputClient;
+import android.tv.ITvInputManager;
+import android.tv.ITvInputService;
+import android.tv.ITvInputServiceCallback;
+import android.tv.ITvInputSession;
+import android.tv.ITvInputSessionCallback;
+import android.tv.TvInputInfo;
+import android.tv.TvInputService;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Surface;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.server.SystemService;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** This class provides a system service that manages television inputs. */
+public final class TvInputManagerService extends SystemService {
+ // STOPSHIP: Turn debugging off.
+ private static final boolean DEBUG = true;
+ private static final String TAG = "TvInputManagerService";
+
+ private final Context mContext;
+
+ // A global lock.
+ private final Object mLock = new Object();
+
+ // ID of the current user.
+ private int mCurrentUserId = UserHandle.USER_OWNER;
+
+ // A map from user id to UserState.
+ private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
+
+ public TvInputManagerService(Context context) {
+ super(context);
+ mContext = context;
+ registerBroadcastReceivers();
+ synchronized (mLock) {
+ mUserStates.put(mCurrentUserId, new UserState());
+ buildTvInputListLocked(mCurrentUserId);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
+ }
+
+ private void registerBroadcastReceivers() {
+ PackageMonitor monitor = new PackageMonitor() {
+ @Override
+ public void onSomePackagesChanged() {
+ synchronized (mLock) {
+ buildTvInputListLocked(mCurrentUserId);
+ }
+ }
+ };
+ monitor.register(mContext, null, UserHandle.ALL, true);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+ }
+ }
+ }, UserHandle.ALL, intentFilter, null, null);
+ }
+
+ private void buildTvInputListLocked(int userId) {
+ UserState userState = getUserStateLocked(userId);
+ userState.inputList.clear();
+
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> services = pm.queryIntentServices(
+ new Intent(TvInputService.SERVICE_INTERFACE), PackageManager.GET_SERVICES);
+ for (ResolveInfo ri : services) {
+ ServiceInfo si = ri.serviceInfo;
+ if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
+ Log.w(TAG, "Skipping TV input " + si.name + ": it does not require the permission "
+ + android.Manifest.permission.BIND_TV_INPUT);
+ continue;
+ }
+ TvInputInfo info = new TvInputInfo(ri);
+ userState.inputList.add(info);
+ }
+ }
+
+ private void switchUser(int userId) {
+ synchronized (mLock) {
+ if (mCurrentUserId == userId) {
+ return;
+ }
+ // final int oldUserId = mCurrentUserId;
+ // TODO: Release services and sessions in the old user state, if needed.
+ mCurrentUserId = userId;
+
+ UserState userState = mUserStates.get(userId);
+ if (userState == null) {
+ userState = new UserState();
+ }
+ mUserStates.put(userId, userState);
+ buildTvInputListLocked(userId);
+ }
+ }
+
+ private void removeUser(int userId) {
+ synchronized (mLock) {
+ // Release created sessions.
+ UserState userState = getUserStateLocked(userId);
+ for (SessionState state : userState.sessionStateMap.values()) {
+ if (state.session != null) {
+ try {
+ state.session.release();
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in release", e);
+ }
+ }
+ }
+ userState.sessionStateMap.clear();
+
+ // Unregister all callbacks and unbind all services.
+ for (ServiceState serviceState : userState.serviceStateMap.values()) {
+ if (serviceState.callback != null) {
+ try {
+ serviceState.service.unregisterCallback(serviceState.callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in unregisterCallback", e);
+ }
+ }
+ serviceState.clients.clear();
+ mContext.unbindService(serviceState.connection);
+ }
+ userState.serviceStateMap.clear();
+
+ mUserStates.remove(userId);
+ }
+ }
+
+ private UserState getUserStateLocked(int userId) {
+ UserState userState = mUserStates.get(userId);
+ if (userState == null) {
+ throw new IllegalStateException("User state not found for user ID " + userId);
+ }
+ return userState;
+ }
+
+ private ServiceState getServiceStateLocked(ComponentName name, int userId) {
+ UserState userState = getUserStateLocked(userId);
+ ServiceState serviceState = userState.serviceStateMap.get(name);
+ if (serviceState == null) {
+ throw new IllegalStateException("Service state not found for " + name + " (userId=" +
+ userId + ")");
+ }
+ return serviceState;
+ }
+
+ private ITvInputSession getSessionLocked(IBinder sessionToken, int callingUid, int userId) {
+ UserState userState = getUserStateLocked(userId);
+ SessionState sessionState = userState.sessionStateMap.get(sessionToken);
+ if (sessionState == null) {
+ throw new IllegalArgumentException("Session state not found for token " + sessionToken);
+ }
+ // Only the application that requested this session or the system can access it.
+ if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.callingUid) {
+ throw new SecurityException("Illegal access to the session with token " + sessionToken
+ + " from uid " + callingUid);
+ }
+ ITvInputSession session = sessionState.session;
+ if (session == null) {
+ throw new IllegalStateException("Session not yet created for token " + sessionToken);
+ }
+ return session;
+ }
+
+ private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
+ String methodName) {
+ return ActivityManager.handleIncomingUser(callingPid, callingUid, requestedUserId, false,
+ false, methodName, null);
+ }
+
+ private void updateServiceConnectionLocked(ComponentName name, int userId) {
+ UserState userState = getUserStateLocked(userId);
+ ServiceState serviceState = userState.serviceStateMap.get(name);
+ if (serviceState == null) {
+ return;
+ }
+ boolean isStateEmpty = serviceState.clients.size() == 0
+ && serviceState.sessionStateMap.size() == 0;
+ if (serviceState.service == null && !isStateEmpty && userId == mCurrentUserId) {
+ // This means that the service is not yet connected but its state indicates that we
+ // have pending requests. Then, connect the service.
+ if (serviceState.bound) {
+ // We have already bound to the service so we don't try to bind again until after we
+ // unbind later on.
+ return;
+ }
+ if (DEBUG) {
+ Log.i(TAG, "bindServiceAsUser(name=" + name.getClassName() + ", userId=" + userId
+ + ")");
+ }
+ Intent i = new Intent(TvInputService.SERVICE_INTERFACE).setComponent(name);
+ mContext.bindServiceAsUser(i, serviceState.connection, Context.BIND_AUTO_CREATE,
+ new UserHandle(userId));
+ serviceState.bound = true;
+ } else if (serviceState.service != null && isStateEmpty) {
+ // This means that the service is already connected but its state indicates that we have
+ // nothing to do with it. Then, disconnect the service.
+ if (DEBUG) {
+ Log.i(TAG, "unbindService(name=" + name.getClassName() + ")");
+ }
+ mContext.unbindService(serviceState.connection);
+ userState.serviceStateMap.remove(name);
+ }
+ }
+
+ private void createSessionInternalLocked(ITvInputService service, final IBinder sessionToken,
+ final SessionState sessionState, final int userId) {
+ if (DEBUG) {
+ Log.d(TAG, "createSessionInternalLocked(name=" + sessionState.name.getClassName()
+ + ")");
+ }
+ // Set up a callback to send the session token.
+ ITvInputSessionCallback callback = new ITvInputSessionCallback.Stub() {
+ @Override
+ public void onSessionCreated(ITvInputSession session) {
+ if (DEBUG) {
+ Log.d(TAG, "onSessionCreated(name=" + sessionState.name.getClassName() + ")");
+ }
+ synchronized (mLock) {
+ sessionState.session = session;
+ sendSessionTokenToClientLocked(sessionState.client, sessionState.name,
+ sessionToken, sessionState.seq, userId);
+ }
+ }
+ };
+
+ // Create a session. When failed, send a null token immediately.
+ try {
+ service.createSession(callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in createSession", e);
+ sendSessionTokenToClientLocked(sessionState.client, sessionState.name, null,
+ sessionState.seq, userId);
+ }
+ }
+
+ private void sendSessionTokenToClientLocked(ITvInputClient client, ComponentName name,
+ IBinder sessionToken, int seq, int userId) {
+ try {
+ client.onSessionCreated(name, sessionToken, seq);
+ } catch (RemoteException exception) {
+ Log.e(TAG, "error in onSessionCreated", exception);
+ }
+
+ if (sessionToken == null) {
+ // This means that the session creation failed. We might want to disconnect the service.
+ updateServiceConnectionLocked(name, userId);
+ }
+ }
+
+ private final class BinderService extends ITvInputManager.Stub {
+ @Override
+ public List<TvInputInfo> getTvInputList(int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "getTvInputList");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(resolvedUserId);
+ return new ArrayList<TvInputInfo>(userState.inputList);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public boolean getAvailability(final ITvInputClient client, final ComponentName name,
+ int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "getAvailability");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(resolvedUserId);
+ ServiceState serviceState = userState.serviceStateMap.get(name);
+ if (serviceState != null) {
+ // We already know the status of this input service. Return the cached
+ // status.
+ return serviceState.available;
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return false;
+ }
+
+ @Override
+ public void registerCallback(final ITvInputClient client, final ComponentName name,
+ int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "registerCallback");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ // Create a new service callback and add it to the callback map of the current
+ // service.
+ UserState userState = getUserStateLocked(resolvedUserId);
+ ServiceState serviceState = userState.serviceStateMap.get(name);
+ if (serviceState == null) {
+ serviceState = new ServiceState(name, resolvedUserId);
+ userState.serviceStateMap.put(name, serviceState);
+ }
+ IBinder iBinder = client.asBinder();
+ if (!serviceState.clients.contains(iBinder)) {
+ serviceState.clients.add(iBinder);
+ }
+ if (serviceState.service != null) {
+ if (serviceState.callback != null) {
+ // We already handled.
+ return;
+ }
+ serviceState.callback = new ServiceCallback(resolvedUserId);
+ try {
+ serviceState.service.registerCallback(serviceState.callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in registerCallback", e);
+ }
+ } else {
+ updateServiceConnectionLocked(name, resolvedUserId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void unregisterCallback(ITvInputClient client, ComponentName name, int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "unregisterCallback");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(resolvedUserId);
+ ServiceState serviceState = userState.serviceStateMap.get(name);
+ if (serviceState == null) {
+ return;
+ }
+
+ // Remove this client from the client list and unregister the callback.
+ serviceState.clients.remove(client.asBinder());
+ if (!serviceState.clients.isEmpty()) {
+ // We have other clients who want to keep the callback. Do this later.
+ return;
+ }
+ if (serviceState.service == null || serviceState.callback == null) {
+ return;
+ }
+ try {
+ serviceState.service.unregisterCallback(serviceState.callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in unregisterCallback", e);
+ } finally {
+ serviceState.callback = null;
+ updateServiceConnectionLocked(name, resolvedUserId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void createSession(final ITvInputClient client, final ComponentName name,
+ int seq, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "createSession");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ // Create a new session token and a session state.
+ IBinder sessionToken = new Binder();
+ SessionState sessionState = new SessionState(name, client, seq, callingUid);
+ sessionState.session = null;
+
+ // Add them to the global session state map of the current user.
+ UserState userState = getUserStateLocked(resolvedUserId);
+ userState.sessionStateMap.put(sessionToken, sessionState);
+
+ // Also, add them to the session state map of the current service.
+ ServiceState serviceState = userState.serviceStateMap.get(name);
+ if (serviceState == null) {
+ serviceState = new ServiceState(name, resolvedUserId);
+ userState.serviceStateMap.put(name, serviceState);
+ }
+ serviceState.sessionStateMap.put(sessionToken, sessionState);
+
+ if (serviceState.service != null) {
+ createSessionInternalLocked(serviceState.service, sessionToken,
+ sessionState, resolvedUserId);
+ } else {
+ updateServiceConnectionLocked(name, resolvedUserId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void releaseSession(IBinder sessionToken, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "releaseSession");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ // Release the session.
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId).release();
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in release", e);
+ }
+
+ // Remove its state from the global session state map of the current user.
+ UserState userState = getUserStateLocked(resolvedUserId);
+ SessionState sessionState = userState.sessionStateMap.remove(sessionToken);
+
+ // Also remove it from the session state map of the current service.
+ ServiceState serviceState = userState.serviceStateMap.get(sessionState.name);
+ if (serviceState != null) {
+ serviceState.sessionStateMap.remove(sessionToken);
+ }
+
+ updateServiceConnectionLocked(sessionState.name, resolvedUserId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setSurface(IBinder sessionToken, Surface surface, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setSurface");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId).setSurface(
+ surface);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in setSurface", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setVolume(IBinder sessionToken, float volume, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setVolume");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId).setVolume(
+ volume);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in setVolume", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void tune(IBinder sessionToken, final Uri channelUri, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "tune");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ SessionState sessionState = getUserStateLocked(resolvedUserId)
+ .sessionStateMap.get(sessionToken);
+ final String serviceName = sessionState.name.getClassName();
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId).tune(channelUri);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in tune", e);
+ return;
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ private static final class UserState {
+ // A list of all known TV inputs on the system.
+ private final List<TvInputInfo> inputList = new ArrayList<TvInputInfo>();
+
+ // A mapping from the name of a TV input service to its state.
+ private final Map<ComponentName, ServiceState> serviceStateMap =
+ new HashMap<ComponentName, ServiceState>();
+
+ // A mapping from the token of a TV input session to its state.
+ private final Map<IBinder, SessionState> sessionStateMap =
+ new HashMap<IBinder, SessionState>();
+ }
+
+ private final class ServiceState {
+ private final List<IBinder> clients = new ArrayList<IBinder>();
+ private final ArrayMap<IBinder, SessionState> sessionStateMap = new ArrayMap<IBinder,
+ SessionState>();
+ private final ServiceConnection connection;
+
+ private ITvInputService service;
+ private ServiceCallback callback;
+ private boolean bound;
+ private boolean available;
+
+ private ServiceState(ComponentName name, int userId) {
+ this.connection = new InputServiceConnection(userId);
+ }
+ }
+
+ private static final class SessionState {
+ private final ComponentName name;
+ private final ITvInputClient client;
+ private final int seq;
+ private final int callingUid;
+
+ private ITvInputSession session;
+
+ private SessionState(ComponentName name, ITvInputClient client, int seq, int callingUid) {
+ this.name = name;
+ this.client = client;
+ this.seq = seq;
+ this.callingUid = callingUid;
+ }
+ }
+
+ private final class InputServiceConnection implements ServiceConnection {
+ private final int mUserId;
+
+ private InputServiceConnection(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) {
+ Log.d(TAG, "onServiceConnected(name=" + name.getClassName() + ")");
+ }
+ synchronized (mLock) {
+ ServiceState serviceState = getServiceStateLocked(name, mUserId);
+ serviceState.service = ITvInputService.Stub.asInterface(service);
+
+ // Register a callback, if we need to.
+ if (!serviceState.clients.isEmpty() && serviceState.callback == null) {
+ serviceState.callback = new ServiceCallback(mUserId);
+ try {
+ serviceState.service.registerCallback(serviceState.callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in registerCallback", e);
+ }
+ }
+
+ // And create sessions, if any.
+ for (Map.Entry<IBinder, SessionState> entry : serviceState.sessionStateMap
+ .entrySet()) {
+ createSessionInternalLocked(serviceState.service, entry.getKey(),
+ entry.getValue(), mUserId);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) {
+ Log.d(TAG, "onServiceDisconnected(name=" + name.getClassName() + ")");
+ }
+ }
+ }
+
+ private final class ServiceCallback extends ITvInputServiceCallback.Stub {
+ private final int mUserId;
+
+ ServiceCallback(int userId) {
+ mUserId = userId;
+ }
+
+ @Override
+ public void onAvailabilityChanged(ComponentName name, boolean isAvailable)
+ throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "onAvailabilityChanged(name=" + name.getClassName() + ", isAvailable="
+ + isAvailable + ")");
+ }
+ synchronized (mLock) {
+ ServiceState serviceState = getServiceStateLocked(name, mUserId);
+ serviceState.available = isAvailable;
+ for (IBinder iBinder : serviceState.clients) {
+ ITvInputClient client = ITvInputClient.Stub.asInterface(iBinder);
+ client.onAvailabilityChanged(name, isAvailable);
+ }
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 8f2adc8f47b2..bf816864ac6e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -42,6 +42,7 @@ import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.service.dreams.DreamService;
+import android.tv.TvInputManager;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -78,6 +79,7 @@ import com.android.server.search.SearchManagerService;
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.trust.TrustManagerService;
+import com.android.server.tv.TvInputManagerService;
import com.android.server.twilight.TwilightService;
import com.android.server.usb.UsbService;
import com.android.server.wallpaper.WallpaperManagerService;
@@ -906,6 +908,13 @@ public final class SystemServer {
reportWtf("starting HdmiCec Service", e);
}
+ try {
+ Slog.i(TAG, "TvInputManagerService");
+ mSystemServiceManager.startService(TvInputManagerService.class);
+ } catch (Throwable e) {
+ reportWtf("starting TvInputManagerService", e);
+ }
+
if (!disableNonCoreServices) {
try {
Slog.i(TAG, "Media Router Service");