summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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 e1c1547..9cd04eb 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 4f0e603..ffdac4e 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 1dcef29..747b6f2 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 7149ab9..e3b8d5c 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 27e526b..17b57bb 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 0000000..43be6f0
--- /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 0000000..a927dc9
--- /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 0000000..d80f286
--- /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 0000000..e535c81
--- /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 0000000..d379d2d
--- /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 0000000..a2bd0d7
--- /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 0000000..fd4e1e3
--- /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 0000000..abc4b47
--- /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 0000000..90625d8
--- /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 0000000..0b6ab64
--- /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 0000000..e43cc95
--- /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 0000000..5b70a0b
--- /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 3857cd1..0f772f1 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 902aea8..4a121d1 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 0000000..972b088
--- /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 8f2adc8..bf81686 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");