diff --git a/Android.mk b/Android.mk index da53db7c362bbbc15370008de8e36b390c3d027a..1c5b08b640351cf008e24b0a090439f6f1b9c029 100644 --- a/Android.mk +++ b/Android.mk @@ -401,6 +401,8 @@ LOCAL_SRC_FILES += \ media/java/android/media/tv/ITvInputServiceCallback.aidl \ media/java/android/media/tv/ITvInputSession.aidl \ media/java/android/media/tv/ITvInputSessionCallback.aidl \ + media/java/android/media/tv/ITvRemoteProvider.aidl \ + media/java/android/media/tv/ITvRemoteServiceInput.aidl \ media/java/android/service/media/IMediaBrowserService.aidl \ media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl \ telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl \ diff --git a/api/system-current.txt b/api/system-current.txt index 06d95efe91beca34064677c4f1c7a238b276ff46..ecc978d0122deb563a400c7568a8af16f604f9d3 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -51,6 +51,7 @@ package android { 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 = "android.permission.BIND_TRUST_AGENT"; field public static final java.lang.String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT"; + field public static final java.lang.String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE"; field public static final java.lang.String BIND_VOICE_INTERACTION = "android.permission.BIND_VOICE_INTERACTION"; field public static final java.lang.String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE"; field public static final java.lang.String BIND_VR_LISTENER_SERVICE = "android.permission.BIND_VR_LISTENER_SERVICE"; @@ -222,6 +223,7 @@ package android { field public static final java.lang.String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED"; field public static final java.lang.String TRANSMIT_IR = "android.permission.TRANSMIT_IR"; field public static final java.lang.String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE"; + field public static final java.lang.String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER"; field public static final java.lang.String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT"; field public static final java.lang.String UPDATE_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS"; field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS"; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index eb0c7424e3f5e664545e4b477cbe70d65a053f23..0ef72cd7b371d42af0278f73c74643af03e0b8bf 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2279,6 +2279,23 @@ + + + + + + diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index aada05d4bf82afcf1299683a8dccf2b97a4a8eee..789a4174194cdad3ba26dc4737388ade544a8b2e 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2474,4 +2474,8 @@ --> 0 + + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index adeaa63900b6b35387db2cb23014b1816ee1eacd..95f65def580f59b73266b3caf1f27706c16a2434 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2574,4 +2574,7 @@ + + + diff --git a/media/java/android/media/tv/ITvRemoteProvider.aidl b/media/java/android/media/tv/ITvRemoteProvider.aidl new file mode 100644 index 0000000000000000000000000000000000000000..3d9619b045baed08c4e0ae8eb9cc26f14f6a34d3 --- /dev/null +++ b/media/java/android/media/tv/ITvRemoteProvider.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +import android.media.tv.ITvRemoteServiceInput; + +/** + * {@hide} + */ +oneway interface ITvRemoteProvider { + void setRemoteServiceInputSink(in ITvRemoteServiceInput tvServiceInput); + void onInputBridgeConnected(IBinder token); +} \ No newline at end of file diff --git a/media/java/android/media/tv/ITvRemoteServiceInput.aidl b/media/java/android/media/tv/ITvRemoteServiceInput.aidl new file mode 100644 index 0000000000000000000000000000000000000000..df39299c0f93f53998ce6b87f61dbedf340c2fc1 --- /dev/null +++ b/media/java/android/media/tv/ITvRemoteServiceInput.aidl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +/** + * {@hide} + */ +oneway interface ITvRemoteServiceInput { + // InputBridge related + void openInputBridge(IBinder token, String name, int width, int height, int maxPointers); + void closeInputBridge(IBinder token); + void clearInputBridge(IBinder token); + void sendTimestamp(IBinder token, long timestamp); + void sendKeyDown(IBinder token, int keyCode); + void sendKeyUp(IBinder token, int keyCode); + void sendPointerDown(IBinder token, int pointerId, int x, int y); + void sendPointerUp(IBinder token, int pointerId); + void sendPointerSync(IBinder token); +} \ No newline at end of file diff --git a/media/lib/tvremote/Android.mk b/media/lib/tvremote/Android.mk new file mode 100644 index 0000000000000000000000000000000000000000..06838c2dbf97bdcd72cd6e6de0f22eafa38d822e --- /dev/null +++ b/media/lib/tvremote/Android.mk @@ -0,0 +1,48 @@ +# +# Copyright (C) 2016 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. +# +LOCAL_PATH := $(call my-dir) + +# the tvremoteprovider library +# ============================================================ +include $(CLEAR_VARS) + +LOCAL_MODULE:= com.android.media.tv.remoteprovider +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := \ + $(call all-java-files-under, java) \ + $(call all-aidl-files-under, java) + +LOCAL_DEX_PREOPT := false + +include $(BUILD_JAVA_LIBRARY) + + +# ==== com.android.media.tvremote.xml lib def ======================== +include $(CLEAR_VARS) + +LOCAL_MODULE := com.android.media.tv.remoteprovider.xml +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE_CLASS := ETC + +# This will install the file in /system/etc/permissions +# +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions + +LOCAL_SRC_FILES := $(LOCAL_MODULE) + +include $(BUILD_PREBUILT) \ No newline at end of file diff --git a/media/lib/tvremote/README.txt b/media/lib/tvremote/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..9375f02e1cc8b75ebcd382e6dfdfbc4cecd087fa --- /dev/null +++ b/media/lib/tvremote/README.txt @@ -0,0 +1,26 @@ +This library (com.android.media.tv.remoteprovider.jar) is a shared java library +containing classes required by unbundled atv remote providers. + +--- Rules of this library --- +o This library is effectively a System API for unbundled emote service provider + that may be distributed outside the system image. So it MUST BE API STABLE. + You can add but not remove. The rules are the same as for the + public platform SDK API. +o This library can see and instantiate internal platform classes, but it must not + expose them in any public method (or by extending them via inheritance). This would + break clients of the library because they cannot see the internal platform classes. + +This library is distributed in the system image, and loaded as +a shared library. So you can change the implementation, but not +the interface. In this way it is like framework.jar. + +--- Why does this library exist? --- + +Unbundled atv remote providers (such as Emote app) cannot use internal +platform classes. + +This library will eventually be replaced when the inputmanager +infrastructure is ready with APIs allowing unbundled system apps to +inject events into uhid. +That API isn't ready yet so this library is a compromise to +make new capabilities available to the system. \ No newline at end of file diff --git a/media/lib/tvremote/com.android.media.tv.remoteprovider.xml b/media/lib/tvremote/com.android.media.tv.remoteprovider.xml new file mode 100644 index 0000000000000000000000000000000000000000..dcf479ac78b34c52db9dbb054210850e20a1450a --- /dev/null +++ b/media/lib/tvremote/com.android.media.tv.remoteprovider.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java b/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..35322ad8ee6b5fc38d734cf31d97d58fe297318c --- /dev/null +++ b/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2016 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.media.tv.remoteprovider; + +import android.content.Context; +import android.media.tv.ITvRemoteProvider; +import android.media.tv.ITvRemoteServiceInput; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +/** + * Base class for emote providers implemented in unbundled service. + *

+ * This object is not thread safe. It is only intended to be accessed on the + * {@link Context#getMainLooper main looper thread} of an application. + *

+ * IMPORTANT: This class is effectively a system API for unbundled emote service, and + * must remain API stable. See README.txt in the root of this package for more information. + *

+ */ + + +public abstract class TvRemoteProvider { + + /** + * The {@link Intent} that must be declared as handled by the service. + * The service must also require the {@link android.Manifest.permission#BIND_TV_REMOTE_SERVICE} + * permission so that other applications cannot abuse it. + */ + public static final String SERVICE_INTERFACE = + "com.android.media.tv.remoteprovider.TvRemoteProvider"; + + private static final String TAG = "TvRemoteProvider"; + private static final boolean DEBUG_KEYS = false; + private static final int MSG_SET_SERVICE_INPUT = 1; + private static final int MSG_SEND_INPUTBRIDGE_CONNECTED = 2; + private final Context mContext; + private final ProviderStub mStub; + private final ProviderHandler mHandler; + private ITvRemoteServiceInput mRemoteServiceInput; + + /** + * Creates a provider for an unbundled emote controller + * service allowing it to interface with the tv remote controller + * system service. + * + * @param context The application context for the remote provider. + */ + public TvRemoteProvider(Context context) { + mContext = context.getApplicationContext(); + mStub = new ProviderStub(); + mHandler = new ProviderHandler(mContext.getMainLooper()); + } + + /** + * Gets the context of the remote service provider. + */ + public final Context getContext() { + return mContext; + } + + + /** + * Gets the Binder associated with the provider. + *

+ * This is intended to be used for the onBind() method of a service that implements + * a remote provider service. + *

+ * + * @return The IBinder instance associated with the provider. + */ + public IBinder getBinder() { + return mStub; + } + + /** + * Information about the InputBridge connected status. + * + * @param token Identifier for the connection. Null, if failed. + */ + public void onInputBridgeConnected(IBinder token) { + } + + /** + * Set a sink for sending events to framework service. + * + * @param tvServiceInput sink defined in framework service + */ + private void setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput) { + mRemoteServiceInput = tvServiceInput; + } + + /** + * openRemoteInputBridge : Open an input bridge for a particular device. + * Clients should pass in a token that can be used to match this request with a token that + * will be returned by {@link TvRemoteProvider#onInputBridgeConnected(IBinder token)} + *

+ * The token should be used for subsequent calls. + *

+ * + * @param name Device name + * @param token Identifier for this connection + * @param width Width of the device's virtual touchpad + * @param height Height of the device's virtual touchpad + * @param maxPointers Maximum supported pointers + * @throws RuntimeException + */ + public void openRemoteInputBridge(IBinder token, String name, int width, int height, + int maxPointers) throws RuntimeException { + try { + mRemoteServiceInput.openInputBridge(token, name, width, height, maxPointers); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * closeInputBridge : Close input bridge for a device + * + * @param token identifier for this connection + * @throws RuntimeException + */ + public void closeInputBridge(IBinder token) throws RuntimeException { + try { + mRemoteServiceInput.closeInputBridge(token); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * clearInputBridge : Clear out any existing key or pointer events in queue for this device by + * dropping them on the floor and sending an UP to all keys and pointer + * slots. + * + * @param token identifier for this connection + * @throws RuntimeException + */ + public void clearInputBridge(IBinder token) throws RuntimeException { + if (DEBUG_KEYS) Log.d(TAG, "clearInputBridge() token " + token); + try { + mRemoteServiceInput.clearInputBridge(token); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * sendTimestamp : Send a timestamp for a set of pointer events + * + * @param token identifier for the device + * @param timestamp Timestamp to be used in + * {@link android.os.SystemClock#uptimeMillis} time base + * @throws RuntimeException + */ + public void sendTimestamp(IBinder token, long timestamp) throws RuntimeException { + if (DEBUG_KEYS) Log.d(TAG, "sendTimestamp() token: " + token + + ", timestamp: " + timestamp); + try { + mRemoteServiceInput.sendTimestamp(token, timestamp); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * sendKeyUp : Send key up event for a device + * + * @param token identifier for this connection + * @param keyCode Key code to be sent + * @throws RuntimeException + */ + public void sendKeyUp(IBinder token, int keyCode) throws RuntimeException { + if (DEBUG_KEYS) Log.d(TAG, "sendKeyUp() token: " + token + ", keyCode: " + keyCode); + try { + mRemoteServiceInput.sendKeyUp(token, keyCode); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * sendKeyDown : Send key down event for a device + * + * @param token identifier for this connection + * @param keyCode Key code to be sent + * @throws RuntimeException + */ + public void sendKeyDown(IBinder token, int keyCode) throws RuntimeException { + if (DEBUG_KEYS) Log.d(TAG, "sendKeyDown() token: " + token + + ", keyCode: " + keyCode); + try { + mRemoteServiceInput.sendKeyDown(token, keyCode); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * sendPointerUp : Send pointer up event for a device + * + * @param token identifier for the device + * @param pointerId Pointer id to be used. Value may be from 0 + * to {@link MotionEvent#getPointerCount()} -1 + * @throws RuntimeException + */ + public void sendPointerUp(IBinder token, int pointerId) throws RuntimeException { + if (DEBUG_KEYS) Log.d(TAG, "sendPointerUp() token: " + token + + ", pointerId: " + pointerId); + try { + mRemoteServiceInput.sendPointerUp(token, pointerId); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * sendPointerDown : Send pointer down event for a device + * + * @param token identifier for the device + * @param pointerId Pointer id to be used. Value may be from 0 + * to {@link MotionEvent#getPointerCount()} -1 + * @param x X co-ordinates in display pixels + * @param y Y co-ordinates in display pixels + * @throws RuntimeException + */ + public void sendPointerDown(IBinder token, int pointerId, int x, int y) + throws RuntimeException { + if (DEBUG_KEYS) Log.d(TAG, "sendPointerDown() token: " + token + + ", pointerId: " + pointerId); + try { + mRemoteServiceInput.sendPointerDown(token, pointerId, x, y); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * sendPointerSync : Send pointer sync event for a device + * + * @param token identifier for the device + * @throws RuntimeException + */ + public void sendPointerSync(IBinder token) throws RuntimeException { + if (DEBUG_KEYS) Log.d(TAG, "sendPointerSync() token: " + token); + try { + mRemoteServiceInput.sendPointerSync(token); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + private final class ProviderStub extends ITvRemoteProvider.Stub { + @Override + public void setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput) { + mHandler.obtainMessage(MSG_SET_SERVICE_INPUT, tvServiceInput).sendToTarget(); + } + + @Override + public void onInputBridgeConnected(IBinder token) { + mHandler.obtainMessage(MSG_SEND_INPUTBRIDGE_CONNECTED, 0, 0, + (IBinder) token).sendToTarget(); + } + } + + private final class ProviderHandler extends Handler { + public ProviderHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SET_SERVICE_INPUT: { + setRemoteServiceInputSink((ITvRemoteServiceInput) msg.obj); + break; + } + case MSG_SEND_INPUTBRIDGE_CONNECTED: { + onInputBridgeConnected((IBinder) msg.obj); + break; + } + } + } + } +} diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java new file mode 100644 index 0000000000000000000000000000000000000000..557e9c8c85577d764f033fd5bc42fd85e5dd14d9 --- /dev/null +++ b/services/core/java/com/android/server/tv/TvRemoteProviderProxy.java @@ -0,0 +1,647 @@ +/* + * Copyright (C) 2016 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.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.media.tv.ITvRemoteProvider; +import android.media.tv.ITvRemoteServiceInput; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; +import android.util.Slog; + +import java.io.PrintWriter; +import java.lang.ref.WeakReference; + +/** + * Maintains a connection to a tv remote provider service. + */ +final class TvRemoteProviderProxy implements ServiceConnection { + private static final String TAG = "TvRemoteProvProxy"; // max. 23 chars + private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE); + private static final boolean DEBUG_KEY = false; + + + // This should match TvRemoteProvider.ACTION_TV_REMOTE_PROVIDER + protected static final String SERVICE_INTERFACE = + "com.android.media.tv.remoteprovider.TvRemoteProvider"; + private final Context mContext; + private final ComponentName mComponentName; + private final int mUserId; + private final int mUid; + private final Handler mHandler; + + /** + * State guarded by mLock. + * This is the first lock in sequence for an incoming call. + * The second lock is always {@link TvRemoteService#mLock} + * + * There are currently no methods that break this sequence. + */ + private final Object mLock = new Object(); + + private ProviderMethods mProviderMethods; + // Connection state + private boolean mRunning; + private boolean mBound; + private Connection mActiveConnection; + private boolean mConnectionReady; + + public TvRemoteProviderProxy(Context context, ComponentName componentName, int userId, + int uid) { + mContext = context; + mComponentName = componentName; + mUserId = userId; + mUid = uid; + mHandler = new Handler(); + } + + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "Proxy"); + pw.println(prefix + " mUserId=" + mUserId); + pw.println(prefix + " mRunning=" + mRunning); + pw.println(prefix + " mBound=" + mBound); + pw.println(prefix + " mActiveConnection=" + mActiveConnection); + pw.println(prefix + " mConnectionReady=" + mConnectionReady); + } + + public void setProviderSink(ProviderMethods provider) { + mProviderMethods = provider; + } + + public boolean hasComponentName(String packageName, String className) { + return mComponentName.getPackageName().equals(packageName) + && mComponentName.getClassName().equals(className); + } + + public void start() { + if (!mRunning) { + if (DEBUG) { + Slog.d(TAG, this + ": Starting"); + } + + mRunning = true; + updateBinding(); + } + } + + public void stop() { + if (mRunning) { + if (DEBUG) { + Slog.d(TAG, this + ": Stopping"); + } + + mRunning = false; + updateBinding(); + } + } + + public void rebindIfDisconnected() { + synchronized (mLock) { + if (mActiveConnection == null && shouldBind()) { + unbind(); + bind(); + } + } + } + + private void updateBinding() { + if (shouldBind()) { + bind(); + } else { + unbind(); + } + } + + private boolean shouldBind() { + return mRunning; + } + + private void bind() { + if (!mBound) { + if (DEBUG) { + Slog.d(TAG, this + ": Binding"); + } + + Intent service = new Intent(SERVICE_INTERFACE); + service.setComponent(mComponentName); + try { + mBound = mContext.bindServiceAsUser(service, this, + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + new UserHandle(mUserId)); + if (!mBound && DEBUG) { + Slog.d(TAG, this + ": Bind failed"); + } + } catch (SecurityException ex) { + if (DEBUG) { + Slog.d(TAG, this + ": Bind failed", ex); + } + } + } + } + + private void unbind() { + if (mBound) { + if (DEBUG) { + Slog.d(TAG, this + ": Unbinding"); + } + + mBound = false; + disconnect(); + mContext.unbindService(this); + } + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG) { + Slog.d(TAG, this + ": onServiceConnected()"); + } + + if (mBound) { + disconnect(); + + ITvRemoteProvider provider = ITvRemoteProvider.Stub.asInterface(service); + if (provider != null) { + Connection connection = new Connection(provider); + if (connection.register()) { + synchronized (mLock) { + mActiveConnection = connection; + } + if (DEBUG) { + Slog.d(TAG, this + ": Connected successfully."); + } + } else { + if (DEBUG) { + Slog.d(TAG, this + ": Registration failed"); + } + } + } else { + Slog.e(TAG, this + ": Service returned invalid remote-control provider binder"); + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + if (DEBUG) Slog.d(TAG, this + ": Service disconnected"); + disconnect(); + } + + + private void onConnectionReady(Connection connection) { + synchronized (mLock) { + if (DEBUG) Slog.d(TAG, "onConnectionReady"); + if (mActiveConnection == connection) { + if (DEBUG) Slog.d(TAG, "mConnectionReady = true"); + mConnectionReady = true; + } + } + } + + private void onConnectionDied(Connection connection) { + if (mActiveConnection == connection) { + if (DEBUG) Slog.d(TAG, this + ": Service connection died"); + disconnect(); + } + } + + private void disconnect() { + synchronized (mLock) { + if (mActiveConnection != null) { + mConnectionReady = false; + mActiveConnection.dispose(); + mActiveConnection = null; + } + } + } + + // Provider helpers + public void inputBridgeConnected(IBinder token) { + synchronized (mLock) { + if (DEBUG) Slog.d(TAG, this + ": inputBridgeConnected token: " + token); + if (mConnectionReady) { + mActiveConnection.onInputBridgeConnected(token); + } + } + } + + public interface ProviderMethods { + // InputBridge + void openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name, + int width, int height, int maxPointers); + + void closeInputBridge(TvRemoteProviderProxy provider, IBinder token); + + void clearInputBridge(TvRemoteProviderProxy provider, IBinder token); + + void sendTimeStamp(TvRemoteProviderProxy provider, IBinder token, long timestamp); + + void sendKeyDown(TvRemoteProviderProxy provider, IBinder token, int keyCode); + + void sendKeyUp(TvRemoteProviderProxy provider, IBinder token, int keyCode); + + void sendPointerDown(TvRemoteProviderProxy provider, IBinder token, int pointerId, int x, + int y); + + void sendPointerUp(TvRemoteProviderProxy provider, IBinder token, int pointerId); + + void sendPointerSync(TvRemoteProviderProxy provider, IBinder token); + } + + private final class Connection implements IBinder.DeathRecipient { + private final ITvRemoteProvider mTvRemoteProvider; + private final RemoteServiceInputProvider mServiceInputProvider; + + public Connection(ITvRemoteProvider provider) { + mTvRemoteProvider = provider; + mServiceInputProvider = new RemoteServiceInputProvider(this); + } + + public boolean register() { + if (DEBUG) Slog.d(TAG, "Connection::register()"); + try { + mTvRemoteProvider.asBinder().linkToDeath(this, 0); + mTvRemoteProvider.setRemoteServiceInputSink(mServiceInputProvider); + mHandler.post(new Runnable() { + @Override + public void run() { + onConnectionReady(Connection.this); + } + }); + return true; + } catch (RemoteException ex) { + binderDied(); + } + return false; + } + + public void dispose() { + if (DEBUG) Slog.d(TAG, "Connection::dispose()"); + mTvRemoteProvider.asBinder().unlinkToDeath(this, 0); + mServiceInputProvider.dispose(); + } + + + public void onInputBridgeConnected(IBinder token) { + if (DEBUG) Slog.d(TAG, this + ": onInputBridgeConnected"); + try { + mTvRemoteProvider.onInputBridgeConnected(token); + } catch (RemoteException ex) { + Slog.e(TAG, "Failed to deliver onInputBridgeConnected. ", ex); + } + } + + @Override + public void binderDied() { + mHandler.post(new Runnable() { + @Override + public void run() { + onConnectionDied(Connection.this); + } + }); + } + + void openInputBridge(final IBinder token, final String name, final int width, + final int height, final int maxPointers) { + synchronized (mLock) { + if (mActiveConnection == this && Binder.getCallingUid() == mUid) { + if (DEBUG) { + Slog.d(TAG, this + ": openInputBridge," + + " token=" + token + ", name=" + name); + } + final long idToken = Binder.clearCallingIdentity(); + try { + if (mProviderMethods != null) { + mProviderMethods.openInputBridge(TvRemoteProviderProxy.this, token, + name, width, height, maxPointers); + } + } finally { + Binder.restoreCallingIdentity(idToken); + } + } else { + if (DEBUG) { + Slog.w(TAG, + "openInputBridge, Invalid connection or incorrect uid: " + Binder + .getCallingUid()); + } + } + } + } + + void closeInputBridge(final IBinder token) { + synchronized (mLock) { + if (mActiveConnection == this && Binder.getCallingUid() == mUid) { + if (DEBUG) { + Slog.d(TAG, this + ": closeInputBridge," + + " token=" + token); + } + final long idToken = Binder.clearCallingIdentity(); + try { + if (mProviderMethods != null) { + mProviderMethods.closeInputBridge(TvRemoteProviderProxy.this, token); + } + } finally { + Binder.restoreCallingIdentity(idToken); + } + } else { + if (DEBUG) { + Slog.w(TAG, + "closeInputBridge, Invalid connection or incorrect uid: " + + Binder.getCallingUid()); + } + } + } + } + + void clearInputBridge(final IBinder token) { + synchronized (mLock) { + if (mActiveConnection == this && Binder.getCallingUid() == mUid) { + if (DEBUG) { + Slog.d(TAG, this + ": clearInputBridge," + + " token=" + token); + } + final long idToken = Binder.clearCallingIdentity(); + try { + if (mProviderMethods != null) { + mProviderMethods.clearInputBridge(TvRemoteProviderProxy.this, token); + } + } finally { + Binder.restoreCallingIdentity(idToken); + } + } else { + if (DEBUG) { + Slog.w(TAG, + "clearInputBridge, Invalid connection or incorrect uid: " + + Binder.getCallingUid()); + } + } + } + } + + void sendTimestamp(final IBinder token, final long timestamp) { + synchronized (mLock) { + if (mActiveConnection == this && Binder.getCallingUid() == mUid) { + final long idToken = Binder.clearCallingIdentity(); + try { + if (mProviderMethods != null) { + mProviderMethods.sendTimeStamp(TvRemoteProviderProxy.this, token, + timestamp); + } + } finally { + Binder.restoreCallingIdentity(idToken); + } + } else { + if (DEBUG) { + Slog.w(TAG, + "sendTimeStamp, Invalid connection or incorrect uid: " + Binder + .getCallingUid()); + } + } + } + } + + void sendKeyDown(final IBinder token, final int keyCode) { + synchronized (mLock) { + if (mActiveConnection == this && Binder.getCallingUid() == mUid) { + if (DEBUG_KEY) { + Slog.d(TAG, this + ": sendKeyDown," + + " token=" + token + ", keyCode=" + keyCode); + } + final long idToken = Binder.clearCallingIdentity(); + try { + if (mProviderMethods != null) { + mProviderMethods.sendKeyDown(TvRemoteProviderProxy.this, token, + keyCode); + } + } finally { + Binder.restoreCallingIdentity(idToken); + } + } else { + if (DEBUG) { + Slog.w(TAG, + "sendKeyDown, Invalid connection or incorrect uid: " + Binder + .getCallingUid()); + } + } + } + } + + void sendKeyUp(final IBinder token, final int keyCode) { + synchronized (mLock) { + if (mActiveConnection == this && Binder.getCallingUid() == mUid) { + if (DEBUG_KEY) { + Slog.d(TAG, this + ": sendKeyUp," + + " token=" + token + ", keyCode=" + keyCode); + } + final long idToken = Binder.clearCallingIdentity(); + try { + if (mProviderMethods != null) { + mProviderMethods.sendKeyUp(TvRemoteProviderProxy.this, token, keyCode); + } + } finally { + Binder.restoreCallingIdentity(idToken); + } + } else { + if (DEBUG) { + Slog.w(TAG, + "sendKeyUp, Invalid connection or incorrect uid: " + Binder + .getCallingUid()); + } + } + } + } + + void sendPointerDown(final IBinder token, final int pointerId, final int x, final int y) { + synchronized (mLock) { + if (mActiveConnection == this && Binder.getCallingUid() == mUid) { + if (DEBUG_KEY) { + Slog.d(TAG, this + ": sendPointerDown," + + " token=" + token + ", pointerId=" + pointerId); + } + final long idToken = Binder.clearCallingIdentity(); + try { + if (mProviderMethods != null) { + mProviderMethods.sendPointerDown(TvRemoteProviderProxy.this, token, + pointerId, x, y); + } + } finally { + Binder.restoreCallingIdentity(idToken); + } + } else { + if (DEBUG) { + Slog.w(TAG, + "sendPointerDown, Invalid connection or incorrect uid: " + Binder + .getCallingUid()); + } + } + } + } + + void sendPointerUp(final IBinder token, final int pointerId) { + synchronized (mLock) { + if (mActiveConnection == this && Binder.getCallingUid() == mUid) { + if (DEBUG_KEY) { + Slog.d(TAG, this + ": sendPointerUp," + + " token=" + token + ", pointerId=" + pointerId); + } + final long idToken = Binder.clearCallingIdentity(); + try { + if (mProviderMethods != null) { + mProviderMethods.sendPointerUp(TvRemoteProviderProxy.this, token, + pointerId); + } + } finally { + Binder.restoreCallingIdentity(idToken); + } + } else { + if (DEBUG) { + Slog.w(TAG, + "sendPointerUp, Invalid connection or incorrect uid: " + Binder + .getCallingUid()); + } + } + } + } + + void sendPointerSync(final IBinder token) { + synchronized (mLock) { + if (mActiveConnection == this && Binder.getCallingUid() == mUid) { + if (DEBUG_KEY) { + Slog.d(TAG, this + ": sendPointerSync," + + " token=" + token); + } + final long idToken = Binder.clearCallingIdentity(); + try { + if (mProviderMethods != null) { + mProviderMethods.sendPointerSync(TvRemoteProviderProxy.this, token); + } + } finally { + Binder.restoreCallingIdentity(idToken); + } + } else { + if (DEBUG) { + Slog.w(TAG, + "sendPointerSync, Invalid connection or incorrect uid: " + Binder + .getCallingUid()); + } + } + } + } + } + + /** + * Receives events from the connected provider. + *

+ * This inner class is static and only retains a weak reference to the connection + * to prevent the client from being leaked in case the service is holding an + * active reference to the client's callback. + *

+ */ + private static final class RemoteServiceInputProvider extends ITvRemoteServiceInput.Stub { + private final WeakReference mConnectionRef; + + public RemoteServiceInputProvider(Connection connection) { + mConnectionRef = new WeakReference(connection); + } + + public void dispose() { + // Terminate the connection. + mConnectionRef.clear(); + } + + @Override + public void openInputBridge(IBinder token, String name, int width, + int height, int maxPointers) throws RemoteException { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.openInputBridge(token, name, width, height, maxPointers); + } + } + + @Override + public void closeInputBridge(IBinder token) throws RemoteException { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.closeInputBridge(token); + } + } + + @Override + public void clearInputBridge(IBinder token) throws RemoteException { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.clearInputBridge(token); + } + } + + @Override + public void sendTimestamp(IBinder token, long timestamp) throws RemoteException { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.sendTimestamp(token, timestamp); + } + } + + @Override + public void sendKeyDown(IBinder token, int keyCode) throws RemoteException { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.sendKeyDown(token, keyCode); + } + } + + @Override + public void sendKeyUp(IBinder token, int keyCode) throws RemoteException { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.sendKeyUp(token, keyCode); + } + } + + @Override + public void sendPointerDown(IBinder token, int pointerId, int x, int y) + throws RemoteException { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.sendPointerDown(token, pointerId, x, y); + } + } + + @Override + public void sendPointerUp(IBinder token, int pointerId) throws RemoteException { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.sendPointerUp(token, pointerId); + } + } + + @Override + public void sendPointerSync(IBinder token) throws RemoteException { + Connection connection = mConnectionRef.get(); + if (connection != null) { + connection.sendPointerSync(token); + } + } + } +} diff --git a/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..d27970f4882cfe04d006ce04281ff36fa04d58e9 --- /dev/null +++ b/services/core/java/com/android/server/tv/TvRemoteProviderWatcher.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2016 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.Manifest; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Handler; +import android.os.UserHandle; +import android.util.Log; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.Collections; + +/** + * Watches for emote provider services to be installed. + * Adds a provider for each registered service. + * + * @see TvRemoteProviderProxy + */ +final class TvRemoteProviderWatcher { + + private static final String TAG = "TvRemoteProvWatcher"; // max. 23 chars + private static final boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE); + + private final Context mContext; + private final ProviderMethods mProvider; + private final Handler mHandler; + private final PackageManager mPackageManager; + private final ArrayList mProviderProxies = new ArrayList<>(); + private final int mUserId; + private final String mUnbundledServicePackage; + + private boolean mRunning; + + public TvRemoteProviderWatcher(Context context, ProviderMethods provider, Handler handler) { + mContext = context; + mProvider = provider; + mHandler = handler; + mUserId = UserHandle.myUserId(); + mPackageManager = context.getPackageManager(); + mUnbundledServicePackage = context.getString( + com.android.internal.R.string.config_tvRemoteServicePackage); + } + + public void start() { + if (DEBUG) Slog.d(TAG, "start()"); + if (!mRunning) { + mRunning = true; + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); + filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); + filter.addDataScheme("package"); + mContext.registerReceiverAsUser(mScanPackagesReceiver, + new UserHandle(mUserId), filter, null, mHandler); + + // Scan packages. + // Also has the side-effect of restarting providers if needed. + mHandler.post(mScanPackagesRunnable); + } + } + + public void stop() { + if (mRunning) { + mRunning = false; + + mContext.unregisterReceiver(mScanPackagesReceiver); + mHandler.removeCallbacks(mScanPackagesRunnable); + + // Stop all providers. + for (int i = mProviderProxies.size() - 1; i >= 0; i--) { + mProviderProxies.get(i).stop(); + } + } + } + + private void scanPackages() { + if (!mRunning) { + return; + } + + if (DEBUG) Log.d(TAG, "scanPackages()"); + // Add providers for all new services. + // Reorder the list so that providers left at the end will be the ones to remove. + int targetIndex = 0; + Intent intent = new Intent(TvRemoteProviderProxy.SERVICE_INTERFACE); + for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser( + intent, 0, mUserId)) { + ServiceInfo serviceInfo = resolveInfo.serviceInfo; + if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) { + int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name); + if (sourceIndex < 0) { + TvRemoteProviderProxy providerProxy = + new TvRemoteProviderProxy(mContext, + new ComponentName(serviceInfo.packageName, serviceInfo.name), + mUserId, serviceInfo.applicationInfo.uid); + providerProxy.start(); + mProviderProxies.add(targetIndex++, providerProxy); + mProvider.addProvider(providerProxy); + } else if (sourceIndex >= targetIndex) { + TvRemoteProviderProxy provider = mProviderProxies.get(sourceIndex); + provider.start(); // restart the provider if needed + provider.rebindIfDisconnected(); + Collections.swap(mProviderProxies, sourceIndex, targetIndex++); + } + } + } + if (DEBUG) Log.d(TAG, "scanPackages() targetIndex " + targetIndex); + // Remove providers for missing services. + if (targetIndex < mProviderProxies.size()) { + for (int i = mProviderProxies.size() - 1; i >= targetIndex; i--) { + TvRemoteProviderProxy providerProxy = mProviderProxies.get(i); + mProvider.removeProvider(providerProxy); + mProviderProxies.remove(providerProxy); + providerProxy.stop(); + } + } + } + + private boolean verifyServiceTrusted(ServiceInfo serviceInfo) { + if (serviceInfo.permission == null || !serviceInfo.permission.equals( + Manifest.permission.BIND_TV_REMOTE_SERVICE)) { + // If the service does not require this permission then any app could + // potentially bind to it and cause the atv remote provider service to + // misbehave. So we only want to trust providers that require the + // correct permissions. + Slog.w(TAG, "Ignoring atv remote provider service because it did not " + + "require the BIND_TV_REMOTE_SERVICE permission in its manifest: " + + serviceInfo.packageName + "/" + serviceInfo.name); + return false; + } + + // Check if package name is white-listed here. + if (!serviceInfo.packageName.equals(mUnbundledServicePackage)) { + Slog.w(TAG, "Ignoring atv remote provider service because the package has not " + + "been set and/or whitelisted: " + + serviceInfo.packageName + "/" + serviceInfo.name); + return false; + } + + if (!hasNecessaryPermissions(serviceInfo.packageName)) { + // If the service does not have permission to be + // a virtual tv remote controller, do not trust it. + Slog.w(TAG, "Ignoring atv remote provider service because its package does not " + + "have TV_VIRTUAL_REMOTE_CONTROLLER permission: " + serviceInfo.packageName); + return false; + } + + // Looks good. + return true; + } + + // Returns true only if these permissions are present in calling package. + // Manifest.permission.TV_VIRTUAL_REMOTE_CONTROLLER : virtual remote controller on TV + private boolean hasNecessaryPermissions(String packageName) { + if ((mPackageManager.checkPermission(Manifest.permission.TV_VIRTUAL_REMOTE_CONTROLLER, + packageName) == PackageManager.PERMISSION_GRANTED)) { + return true; + } + return false; + } + + private int findProvider(String packageName, String className) { + int count = mProviderProxies.size(); + for (int i = 0; i < count; i++) { + TvRemoteProviderProxy provider = mProviderProxies.get(i); + if (provider.hasComponentName(packageName, className)) { + return i; + } + } + return -1; + } + + private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) { + Slog.d(TAG, "Received package manager broadcast: " + intent); + } + mHandler.post(mScanPackagesRunnable); + } + }; + + private final Runnable mScanPackagesRunnable = new Runnable() { + @Override + public void run() { + scanPackages(); + } + }; + + public interface ProviderMethods { + void addProvider(TvRemoteProviderProxy providerProxy); + + void removeProvider(TvRemoteProviderProxy providerProxy); + } +} diff --git a/services/core/java/com/android/server/tv/TvRemoteService.java b/services/core/java/com/android/server/tv/TvRemoteService.java new file mode 100644 index 0000000000000000000000000000000000000000..961c9925414abc5313bdd2151dedfaa360746bdd --- /dev/null +++ b/services/core/java/com/android/server/tv/TvRemoteService.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2016 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.content.Context; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.util.ArrayMap; +import android.util.Slog; + +import com.android.server.SystemService; +import com.android.server.Watchdog; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; + +/** + * TvRemoteService represents a system service that allows a connected + * remote control (emote) service to inject white-listed input events + * and call other specified methods for functioning as an emote service. + *

+ * This service is intended for use only by white-listed packages. + */ +public class TvRemoteService extends SystemService implements Watchdog.Monitor { + private static final String TAG = "TvRemoteService"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_KEYS = false; + + private Map mBridgeMap = new ArrayMap(); + private Map mProviderMap = new ArrayMap(); + private ArrayList mProviderList = new ArrayList<>(); + + /** + * State guarded by mLock. + * This is the second lock in sequence for an incoming call. + * The first lock is always {@link TvRemoteProviderProxy#mLock} + * + * There are currently no methods that break this sequence. + * Special note: + * Outgoing call informInputBridgeConnected(), which is called from + * openInputBridgeInternalLocked() uses a handler thereby relinquishing held locks. + */ + private final Object mLock = new Object(); + + public final UserHandler mHandler; + + public TvRemoteService(Context context) { + super(context); + mHandler = new UserHandler(new UserProvider(TvRemoteService.this), context); + Watchdog.getInstance().addMonitor(this); + } + + @Override + public void onStart() { + if (DEBUG) Slog.d(TAG, "onStart()"); + } + + @Override + public void monitor() { + synchronized (mLock) { /* check for deadlock */ } + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { + if (DEBUG) Slog.d(TAG, "PHASE_THIRD_PARTY_APPS_CAN_START"); + mHandler.sendEmptyMessage(UserHandler.MSG_START); + } + } + + //Outgoing calls. + private void informInputBridgeConnected(IBinder token) { + mHandler.obtainMessage(UserHandler.MSG_INPUT_BRIDGE_CONNECTED, 0, 0, token).sendToTarget(); + } + + // Incoming calls. + private void openInputBridgeInternalLocked(TvRemoteProviderProxy provider, IBinder token, + String name, int width, int height, + int maxPointers) { + if (DEBUG) { + Slog.d(TAG, "openInputBridgeInternalLocked(), token: " + token + ", name: " + name + + ", width: " + width + ", height: " + height + ", maxPointers: " + maxPointers); + } + + try { + //Create a new bridge, if one does not exist already + if (mBridgeMap.containsKey(token)) { + if (DEBUG) Slog.d(TAG, "RemoteBridge already exists"); + // Respond back with success. + informInputBridgeConnected(token); + return; + } + + UinputBridge inputBridge = new UinputBridge(token, name, width, height, maxPointers); + + mBridgeMap.put(token, inputBridge); + mProviderMap.put(token, provider); + + // Respond back with success. + informInputBridgeConnected(token); + + } catch (IOException ioe) { + Slog.e(TAG, "Cannot create device for " + name); + } + } + + private void closeInputBridgeInternalLocked(IBinder token) { + if (DEBUG) { + Slog.d(TAG, "closeInputBridgeInternalLocked(), token: " + token); + } + + // Close an existing RemoteBridge + UinputBridge inputBridge = mBridgeMap.get(token); + if (inputBridge != null) { + inputBridge.close(token); + } + + mBridgeMap.remove(token); + } + + + private void clearInputBridgeInternalLocked(IBinder token) { + if (DEBUG) { + Slog.d(TAG, "clearInputBridgeInternalLocked(), token: " + token); + } + + UinputBridge inputBridge = mBridgeMap.get(token); + if (inputBridge != null) { + inputBridge.clear(token); + } + } + + private void sendTimeStampInternalLocked(IBinder token, long timestamp) { + UinputBridge inputBridge = mBridgeMap.get(token); + if (inputBridge != null) { + inputBridge.sendTimestamp(token, timestamp); + } + } + + private void sendKeyDownInternalLocked(IBinder token, int keyCode) { + if (DEBUG_KEYS) { + Slog.d(TAG, "sendKeyDownInternalLocked(), token: " + token + ", keyCode: " + keyCode); + } + + UinputBridge inputBridge = mBridgeMap.get(token); + if (inputBridge != null) { + inputBridge.sendKeyDown(token, keyCode); + } + } + + private void sendKeyUpInternalLocked(IBinder token, int keyCode) { + if (DEBUG_KEYS) { + Slog.d(TAG, "sendKeyUpInternalLocked(), token: " + token + ", keyCode: " + keyCode); + } + + UinputBridge inputBridge = mBridgeMap.get(token); + if (inputBridge != null) { + inputBridge.sendKeyUp(token, keyCode); + } + } + + private void sendPointerDownInternalLocked(IBinder token, int pointerId, int x, int y) { + if (DEBUG_KEYS) { + Slog.d(TAG, "sendPointerDownInternalLocked(), token: " + token + ", pointerId: " + + pointerId + ", x: " + x + ", y: " + y); + } + + UinputBridge inputBridge = mBridgeMap.get(token); + if (inputBridge != null) { + inputBridge.sendPointerDown(token, pointerId, x, y); + } + } + + private void sendPointerUpInternalLocked(IBinder token, int pointerId) { + if (DEBUG_KEYS) { + Slog.d(TAG, "sendPointerUpInternalLocked(), token: " + token + ", pointerId: " + + pointerId); + } + + UinputBridge inputBridge = mBridgeMap.get(token); + if (inputBridge != null) { + inputBridge.sendPointerUp(token, pointerId); + } + } + + private void sendPointerSyncInternalLocked(IBinder token) { + if (DEBUG_KEYS) { + Slog.d(TAG, "sendPointerSyncInternalLocked(), token: " + token); + } + + UinputBridge inputBridge = mBridgeMap.get(token); + if (inputBridge != null) { + inputBridge.sendPointerSync(token); + } + } + + private final class UserHandler extends Handler { + + public static final int MSG_START = 1; + public static final int MSG_INPUT_BRIDGE_CONNECTED = 2; + + private final TvRemoteProviderWatcher mWatcher; + private boolean mRunning; + + public UserHandler(UserProvider provider, Context context) { + super(Looper.getMainLooper(), null, true); + mWatcher = new TvRemoteProviderWatcher(context, provider, this); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START: { + start(); + break; + } + case MSG_INPUT_BRIDGE_CONNECTED: { + IBinder token = (IBinder) msg.obj; + TvRemoteProviderProxy provider = mProviderMap.get(token); + if (provider != null) { + provider.inputBridgeConnected(token); + } + break; + } + } + } + + private void start() { + if (!mRunning) { + mRunning = true; + mWatcher.start(); // also starts all providers + } + } + } + + private final class UserProvider implements TvRemoteProviderWatcher.ProviderMethods, + TvRemoteProviderProxy.ProviderMethods { + + private final TvRemoteService mService; + + public UserProvider(TvRemoteService service) { + mService = service; + } + + @Override + public void openInputBridge(TvRemoteProviderProxy provider, IBinder token, String name, + int width, int height, int maxPointers) { + if (DEBUG) { + Slog.d(TAG, "openInputBridge(), token: " + token + + ", name: " + name + ", width: " + width + + ", height: " + height + ", maxPointers: " + maxPointers); + } + + synchronized (mLock) { + if (mProviderList.contains(provider)) { + mService.openInputBridgeInternalLocked(provider, token, name, width, height, + maxPointers); + } + } + } + + @Override + public void closeInputBridge(TvRemoteProviderProxy provider, IBinder token) { + if (DEBUG) Slog.d(TAG, "closeInputBridge(), token: " + token); + synchronized (mLock) { + if (mProviderList.contains(provider)) { + mService.closeInputBridgeInternalLocked(token); + mProviderMap.remove(token); + } + } + } + + @Override + public void clearInputBridge(TvRemoteProviderProxy provider, IBinder token) { + if (DEBUG) Slog.d(TAG, "clearInputBridge(), token: " + token); + synchronized (mLock) { + if (mProviderList.contains(provider)) { + mService.clearInputBridgeInternalLocked(token); + } + } + } + + @Override + public void sendTimeStamp(TvRemoteProviderProxy provider, IBinder token, long timestamp) { + synchronized (mLock) { + if (mProviderList.contains(provider)) { + mService.sendTimeStampInternalLocked(token, timestamp); + } + } + } + + @Override + public void sendKeyDown(TvRemoteProviderProxy provider, IBinder token, int keyCode) { + if (DEBUG_KEYS) { + Slog.d(TAG, "sendKeyDown(), token: " + token + ", keyCode: " + keyCode); + } + synchronized (mLock) { + if (mProviderList.contains(provider)) { + mService.sendKeyDownInternalLocked(token, keyCode); + } + } + } + + @Override + public void sendKeyUp(TvRemoteProviderProxy provider, IBinder token, int keyCode) { + if (DEBUG_KEYS) { + Slog.d(TAG, "sendKeyUp(), token: " + token + ", keyCode: " + keyCode); + } + synchronized (mLock) { + if (mProviderList.contains(provider)) { + mService.sendKeyUpInternalLocked(token, keyCode); + } + } + } + + @Override + public void sendPointerDown(TvRemoteProviderProxy provider, IBinder token, int pointerId, + int x, int y) { + if (DEBUG_KEYS) { + Slog.d(TAG, "sendPointerDown(), token: " + token + ", pointerId: " + pointerId); + } + synchronized (mLock) { + if (mProviderList.contains(provider)) { + mService.sendPointerDownInternalLocked(token, pointerId, x, y); + } + } + } + + @Override + public void sendPointerUp(TvRemoteProviderProxy provider, IBinder token, int pointerId) { + if (DEBUG_KEYS) { + Slog.d(TAG, "sendPointerUp(), token: " + token + ", pointerId: " + pointerId); + } + synchronized (mLock) { + if (mProviderList.contains(provider)) { + mService.sendPointerUpInternalLocked(token, pointerId); + } + } + } + + @Override + public void sendPointerSync(TvRemoteProviderProxy provider, IBinder token) { + if (DEBUG_KEYS) Slog.d(TAG, "sendPointerSync(), token: " + token); + synchronized (mLock) { + if (mProviderList.contains(provider)) { + mService.sendPointerSyncInternalLocked(token); + } + } + } + + @Override + public void addProvider(TvRemoteProviderProxy provider) { + if (DEBUG) Slog.d(TAG, "addProvider " + provider); + synchronized (mLock) { + provider.setProviderSink(this); + mProviderList.add(provider); + Slog.d(TAG, "provider: " + provider.toString()); + } + } + + @Override + public void removeProvider(TvRemoteProviderProxy provider) { + if (DEBUG) Slog.d(TAG, "removeProvider " + provider); + synchronized (mLock) { + if (mProviderList.remove(provider) == false) { + Slog.e(TAG, "Unknown provider " + provider); + } + } + } + } +} diff --git a/services/core/java/com/android/server/tv/UinputBridge.java b/services/core/java/com/android/server/tv/UinputBridge.java new file mode 100644 index 0000000000000000000000000000000000000000..f91033281ef82424c066cb4dbc3cd0119d8b7f08 --- /dev/null +++ b/services/core/java/com/android/server/tv/UinputBridge.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2016 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.os.Binder; +import android.os.IBinder; + +import java.io.IOException; + +import dalvik.system.CloseGuard; + +/** + * Sends the input event to the linux driver. + */ +public final class UinputBridge { + private final CloseGuard mCloseGuard = CloseGuard.get(); + private long mPtr; + private IBinder mToken = null; + + private static native long nativeOpen(String name, String uniqueId, int width, int height, + int maxPointers); + private static native void nativeClose(long ptr); + private static native void nativeClear(long ptr); + private static native void nativeSendTimestamp(long ptr, long timestamp); + private static native void nativeSendKey(long ptr, int keyCode, boolean down); + private static native void nativeSendPointerDown(long ptr, int pointerId, int x, int y); + private static native void nativeSendPointerUp(long ptr, int pointerId); + private static native void nativeSendPointerSync(long ptr); + + public UinputBridge(IBinder token, String name, int width, int height, int maxPointers) + throws IOException { + if (width < 1 || height < 1) { + throw new IllegalArgumentException("Touchpad must be at least 1x1."); + } + if (maxPointers < 1 || maxPointers > 32) { + throw new IllegalArgumentException("Touchpad must support between 1 and 32 pointers."); + } + if (token == null) { + throw new IllegalArgumentException("Token cannot be null"); + } + mPtr = nativeOpen(name, token.toString(), width, height, maxPointers); + if (mPtr == 0) { + throw new IOException("Could not open uinput device " + name); + } + mToken = token; + mCloseGuard.open("close"); + } + + @Override + protected void finalize() throws Throwable { + try { + if (mPtr != 0) { + mCloseGuard.warnIfOpen(); + } + close(mToken); + } finally { + mToken = null; + super.finalize(); + } + } + + public void close(IBinder token) { + if (isTokenValid(token)) { + if (mPtr != 0) { + clear(token); + nativeClose(mPtr); + + mPtr = 0; + mCloseGuard.close(); + } + } + } + + public IBinder getToken() { + return mToken; + } + + protected boolean isTokenValid(IBinder token) { + return mToken.equals(token); + } + + public void sendTimestamp(IBinder token, long timestamp) { + if (isTokenValid(token)) { + nativeSendTimestamp(mPtr, timestamp); + } + } + + public void sendKeyDown(IBinder token, int keyCode) { + if (isTokenValid(token)) { + nativeSendKey(mPtr, keyCode, true /*down*/); + } + } + + public void sendKeyUp(IBinder token, int keyCode) { + if (isTokenValid(token)) { + nativeSendKey(mPtr, keyCode, false /*down*/); + } + } + + public void sendPointerDown(IBinder token, int pointerId, int x, int y) { + if (isTokenValid(token)) { + nativeSendPointerDown(mPtr, pointerId, x, y); + } + } + + public void sendPointerUp(IBinder token, int pointerId) { + if (isTokenValid(token)) { + nativeSendPointerUp(mPtr, pointerId); + } + } + + public void sendPointerSync(IBinder token) { + if (isTokenValid(token)) { + nativeSendPointerSync(mPtr); + } + + } + + public void clear(IBinder token) { + if (isTokenValid(token)) { + nativeClear(mPtr); + } + } + +} diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index 5e5c6d9321e3b3a436bb799633216edb91052f8a..a502c3ac7b1a6891acad53ce8c10593dac8acfdb 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -28,6 +28,7 @@ LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \ $(LOCAL_REL_DIR)/com_android_server_SystemServer.cpp \ + $(LOCAL_REL_DIR)/com_android_server_tv_TvUinputBridge.cpp \ $(LOCAL_REL_DIR)/com_android_server_tv_TvInputHal.cpp \ $(LOCAL_REL_DIR)/com_android_server_vr_VrManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.cpp \ diff --git a/services/core/jni/com_android_server_tv_TvKeys.h b/services/core/jni/com_android_server_tv_TvKeys.h new file mode 100644 index 0000000000000000000000000000000000000000..4895f343ad8a999893ae03e090569f40623357c4 --- /dev/null +++ b/services/core/jni/com_android_server_tv_TvKeys.h @@ -0,0 +1,113 @@ +#ifndef ANDROIDTVREMOTE_SERVICE_JNI_KEYS_H_ +#define ANDROIDTVREMOTE_SERVICE_JNI_KEYS_H_ + +#include +#include + +namespace android { + +// Map the keys specified in virtual-remote.kl. +// Only specify the keys actually used in the layout here. +struct Key { + int linuxKeyCode; + int32_t androidKeyCode; +}; + +// List of all of the keycodes that the emote is capable of sending. +static Key KEYS[] = { + // Volume Control + { KEY_VOLUMEDOWN, AKEYCODE_VOLUME_DOWN }, + { KEY_VOLUMEUP, AKEYCODE_VOLUME_UP }, + { KEY_MUTE, AKEYCODE_VOLUME_MUTE }, + { KEY_MUTE, AKEYCODE_MUTE }, + + { KEY_POWER, AKEYCODE_POWER }, + { KEY_HOMEPAGE, AKEYCODE_HOME }, + { KEY_BACK, AKEYCODE_BACK }, + + // Media Control + { KEY_PLAYPAUSE, AKEYCODE_MEDIA_PLAY_PAUSE }, + { KEY_PLAY, AKEYCODE_MEDIA_PLAY }, + { KEY_PAUSECD, AKEYCODE_MEDIA_PAUSE }, + { KEY_NEXTSONG, AKEYCODE_MEDIA_NEXT }, + { KEY_PREVIOUSSONG, AKEYCODE_MEDIA_PREVIOUS }, + { KEY_STOPCD, AKEYCODE_MEDIA_STOP }, + { KEY_RECORD, AKEYCODE_MEDIA_RECORD }, + { KEY_REWIND, AKEYCODE_MEDIA_REWIND }, + { KEY_FASTFORWARD, AKEYCODE_MEDIA_FAST_FORWARD }, + + // TV Control + { KEY_0, AKEYCODE_0 }, + { KEY_1, AKEYCODE_1 }, + { KEY_2, AKEYCODE_2 }, + { KEY_3, AKEYCODE_3 }, + { KEY_4, AKEYCODE_4 }, + { KEY_5, AKEYCODE_5 }, + { KEY_6, AKEYCODE_6 }, + { KEY_7, AKEYCODE_7 }, + { KEY_8, AKEYCODE_8 }, + { KEY_9, AKEYCODE_9 }, + { KEY_BACKSPACE, AKEYCODE_DEL }, + { KEY_ENTER, AKEYCODE_ENTER}, + { KEY_CHANNELUP, AKEYCODE_CHANNEL_UP }, + { KEY_CHANNELDOWN, AKEYCODE_CHANNEL_DOWN }, + + // Old School TV Controls + { KEY_F1, AKEYCODE_F1 }, + { KEY_F2, AKEYCODE_F2 }, + { KEY_F3, AKEYCODE_F3 }, + { KEY_F4, AKEYCODE_F4 }, + { KEY_F5, AKEYCODE_F5 }, + { KEY_F6, AKEYCODE_F6 }, + { KEY_F7, AKEYCODE_F7 }, + { KEY_F8, AKEYCODE_F8 }, + { KEY_F9, AKEYCODE_F9 }, + { KEY_F10, AKEYCODE_F10 }, + { KEY_F11, AKEYCODE_F11 }, + { KEY_F12, AKEYCODE_F12 }, + { KEY_FN_F1, AKEYCODE_F1 }, + { KEY_FN_F2, AKEYCODE_F2 }, + { KEY_FN_F3, AKEYCODE_F3 }, + { KEY_FN_F4, AKEYCODE_F4 }, + { KEY_FN_F5, AKEYCODE_F5 }, + { KEY_FN_F6, AKEYCODE_F6 }, + { KEY_FN_F7, AKEYCODE_F7 }, + { KEY_FN_F8, AKEYCODE_F8 }, + { KEY_FN_F9, AKEYCODE_F9 }, + { KEY_FN_F10, AKEYCODE_F10 }, + { KEY_FN_F11, AKEYCODE_F11 }, + { KEY_FN_F12, AKEYCODE_F12 }, + { KEY_TV, AKEYCODE_TV }, + { KEY_RED, AKEYCODE_PROG_RED }, + { KEY_GREEN, AKEYCODE_PROG_GREEN }, + { KEY_YELLOW, AKEYCODE_PROG_YELLOW }, + { KEY_BLUE, AKEYCODE_PROG_BLUE }, + + { KEY_FAVORITES, AKEYCODE_BUTTON_MODE}, + { KEY_WWW, AKEYCODE_EXPLORER }, + { KEY_MENU, AKEYCODE_MENU }, + { KEY_INFO, AKEYCODE_INFO }, + { KEY_EPG, AKEYCODE_GUIDE }, + { KEY_TEXT, AKEYCODE_TV_TELETEXT }, + { KEY_SUBTITLE, AKEYCODE_CAPTIONS }, + { KEY_PVR, AKEYCODE_DVR}, + { KEY_AUDIO, AKEYCODE_MEDIA_AUDIO_TRACK}, + { KEY_OPTION, AKEYCODE_SETTINGS}, + + // Gamepad buttons + { KEY_UP, AKEYCODE_DPAD_UP }, + { KEY_DOWN, AKEYCODE_DPAD_DOWN }, + { KEY_LEFT, AKEYCODE_DPAD_LEFT }, + { KEY_RIGHT, AKEYCODE_DPAD_RIGHT }, + { KEY_SELECT, AKEYCODE_DPAD_CENTER }, + { BTN_A, AKEYCODE_BUTTON_A }, + { BTN_B, AKEYCODE_BUTTON_B }, + { BTN_X, AKEYCODE_BUTTON_X }, + { BTN_Y, AKEYCODE_BUTTON_Y }, + + { KEY_SEARCH, AKEYCODE_SEARCH }, +}; + +} // namespace android + +#endif // SERVICE_JNI_KEYS_H_ diff --git a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp new file mode 100644 index 0000000000000000000000000000000000000000..de115c80938b531adbf1610b1716fac914c6ddc2 --- /dev/null +++ b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp @@ -0,0 +1,308 @@ +/* + * Copyright 2016, 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. + */ + +#define LOG_TAG "TvRemote-native-uiBridge" + +#include "com_android_server_tv_TvKeys.h" + +#include "jni.h" +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Refer to EventHub.h +#define MSC_ANDROID_TIME_SEC 0x6 +#define MSC_ANDROID_TIME_USEC 0x7 + +#define SLOT_UNKNOWN -1 + +namespace android { + +static std::map keysMap; +static std::map slotsMap; +static BitSet32 mtSlots; + +static void initKeysMap() { + if (keysMap.empty()) { + for (size_t i = 0; i < NELEM(KEYS); i++) { + keysMap[KEYS[i].androidKeyCode] = KEYS[i].linuxKeyCode; + } + } +} + +static int32_t getLinuxKeyCode(int32_t androidKeyCode) { + std::map::iterator it = keysMap.find(androidKeyCode); + if (it != keysMap.end()) { + return it->second; + } + return KEY_UNKNOWN; +} + +static int findSlot(int32_t pointerId) { + std::map::iterator it = slotsMap.find(pointerId); + if (it != slotsMap.end()) { + return it->second; + } + return SLOT_UNKNOWN; +} + +static int assignSlot(int32_t pointerId) { + if (!mtSlots.isFull()) { + uint32_t slot = mtSlots.markFirstUnmarkedBit(); + slotsMap[pointerId] = slot; + return slot; + } + return SLOT_UNKNOWN; +} + +static void unassignSlot(int32_t pointerId) { + int slot = findSlot(pointerId); + if (slot != SLOT_UNKNOWN) { + mtSlots.clearBit(slot); + slotsMap.erase(pointerId); + } +} + +class NativeConnection { +public: + ~NativeConnection(); + + static NativeConnection* open(const char* name, const char* uniqueId, + int32_t width, int32_t height, int32_t maxPointerId); + + void sendEvent(int32_t type, int32_t code, int32_t value); + + int32_t getMaxPointers() const { return mMaxPointers; } + +private: + NativeConnection(int fd, int32_t maxPointers); + + const int mFd; + const int32_t mMaxPointers; +}; + +NativeConnection::NativeConnection(int fd, int32_t maxPointers) : + mFd(fd), mMaxPointers(maxPointers) { +} + +NativeConnection::~NativeConnection() { + ALOGI("Un-Registering uinput device %d.", mFd); + ioctl(mFd, UI_DEV_DESTROY); + close(mFd); +} + +NativeConnection* NativeConnection::open(const char* name, const char* uniqueId, + int32_t width, int32_t height, int32_t maxPointers) { + ALOGI("Registering uinput device %s: touch pad size %dx%d, " + "max pointers %d.", name, width, height, maxPointers); + + int fd = ::open("/dev/uinput", O_WRONLY | O_NDELAY); + if (fd < 0) { + ALOGE("Cannot open /dev/uinput: %s.", strerror(errno)); + return nullptr; + } + + struct uinput_user_dev uinp; + memset(&uinp, 0, sizeof(struct uinput_user_dev)); + strlcpy(uinp.name, name, UINPUT_MAX_NAME_SIZE); + uinp.id.version = 1; + uinp.id.bustype = BUS_VIRTUAL; + + // initialize keymap + initKeysMap(); + + // write device unique id to the phys property + ioctl(fd, UI_SET_PHYS, uniqueId); + + // set the keys mapped + ioctl(fd, UI_SET_EVBIT, EV_KEY); + for (size_t i = 0; i < NELEM(KEYS); i++) { + ioctl(fd, UI_SET_KEYBIT, KEYS[i].linuxKeyCode); + } + + // set the misc events maps + ioctl(fd, UI_SET_EVBIT, EV_MSC); + ioctl(fd, UI_SET_MSCBIT, MSC_ANDROID_TIME_SEC); + ioctl(fd, UI_SET_MSCBIT, MSC_ANDROID_TIME_USEC); + + // register the input device + if (write(fd, &uinp, sizeof(uinp)) != sizeof(uinp)) { + ALOGE("Cannot write uinput_user_dev to fd %d: %s.", fd, strerror(errno)); + close(fd); + return NULL; + } + if (ioctl(fd, UI_DEV_CREATE) != 0) { + ALOGE("Unable to create uinput device: %s.", strerror(errno)); + close(fd); + return nullptr; + } + + ALOGV("Created uinput device, fd=%d.", fd); + return new NativeConnection(fd, maxPointers); +} + +void NativeConnection::sendEvent(int32_t type, int32_t code, int32_t value) { + struct input_event iev; + memset(&iev, 0, sizeof(iev)); + iev.type = type; + iev.code = code; + iev.value = value; + write(mFd, &iev, sizeof(iev)); +} + + +static jlong nativeOpen(JNIEnv* env, jclass clazz, + jstring nameStr, jstring uniqueIdStr, + jint width, jint height, jint maxPointers) { + ScopedUtfChars name(env, nameStr); + ScopedUtfChars uniqueId(env, uniqueIdStr); + + NativeConnection* connection = NativeConnection::open(name.c_str(), uniqueId.c_str(), + width, height, maxPointers); + return reinterpret_cast(connection); +} + +static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) { + NativeConnection* connection = reinterpret_cast(ptr); + delete connection; +} + +static void nativeSendTimestamp(JNIEnv* env, jclass clazz, jlong ptr, jlong timestamp) { + NativeConnection* connection = reinterpret_cast(ptr); + + connection->sendEvent(EV_MSC, MSC_ANDROID_TIME_SEC, timestamp / 1000L); + connection->sendEvent(EV_MSC, MSC_ANDROID_TIME_USEC, (timestamp % 1000L) * 1000L); +} + +static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jboolean down) { + int32_t code = getLinuxKeyCode(keyCode); + NativeConnection* connection = reinterpret_cast(ptr); + if (code != KEY_UNKNOWN) { + connection->sendEvent(EV_KEY, code, down ? 1 : 0); + } else { + ALOGE("Received an unknown keycode of %d.", keyCode); + } +} + +static void nativeSendPointerDown(JNIEnv* env, jclass clazz, jlong ptr, + jint pointerId, jint x, jint y) { + NativeConnection* connection = reinterpret_cast(ptr); + + int32_t slot = findSlot(pointerId); + if (slot == SLOT_UNKNOWN) { + slot = assignSlot(pointerId); + } + if (slot != SLOT_UNKNOWN) { + connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot); + connection->sendEvent(EV_ABS, ABS_MT_TRACKING_ID, pointerId); + connection->sendEvent(EV_ABS, ABS_MT_POSITION_X, x); + connection->sendEvent(EV_ABS, ABS_MT_POSITION_Y, y); + } +} + +static void nativeSendPointerUp(JNIEnv* env, jclass clazz, jlong ptr, + jint pointerId) { + NativeConnection* connection = reinterpret_cast(ptr); + + int32_t slot = findSlot(pointerId); + if (slot != SLOT_UNKNOWN) { + connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot); + connection->sendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1); + unassignSlot(pointerId); + } +} + +static void nativeSendPointerSync(JNIEnv* env, jclass clazz, jlong ptr) { + NativeConnection* connection = reinterpret_cast(ptr); + connection->sendEvent(EV_SYN, SYN_REPORT, 0); +} + +static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) { + NativeConnection* connection = reinterpret_cast(ptr); + + // Clear keys. + for (size_t i = 0; i < NELEM(KEYS); i++) { + connection->sendEvent(EV_KEY, KEYS[i].linuxKeyCode, 0); + } + + // Clear pointers. + int32_t slot = SLOT_UNKNOWN; + for (int32_t i = 0; i < connection->getMaxPointers(); i++) { + slot = findSlot(i); + if (slot != SLOT_UNKNOWN) { + connection->sendEvent(EV_ABS, ABS_MT_SLOT, slot); + connection->sendEvent(EV_ABS, ABS_MT_TRACKING_ID, -1); + } + } + + // Sync pointer events + connection->sendEvent(EV_SYN, SYN_REPORT, 0); +} + +/* + * JNI registration + */ + +static JNINativeMethod gUinputBridgeMethods[] = { + { "nativeOpen", "(Ljava/lang/String;Ljava/lang/String;III)J", + (void*)nativeOpen }, + { "nativeClose", "(J)V", + (void*)nativeClose }, + { "nativeSendTimestamp", "(JJ)V", + (void*)nativeSendTimestamp }, + { "nativeSendKey", "(JIZ)V", + (void*)nativeSendKey }, + { "nativeSendPointerDown", "(JIII)V", + (void*)nativeSendPointerDown }, + { "nativeSendPointerUp", "(JI)V", + (void*)nativeSendPointerUp }, + { "nativeClear", "(J)V", + (void*)nativeClear }, + { "nativeSendPointerSync", "(J)V", + (void*)nativeSendPointerSync }, +}; + +int register_android_server_tv_TvUinputBridge(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "com/android/server/tv/UinputBridge", + gUinputBridgeMethods, NELEM(gUinputBridgeMethods)); + + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + (void)res; // Don't complain about unused variable in the LOG_NDEBUG case + + return 0; +} + +} // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index be99673a4cb50c33f9da2868fa73029fbef47703..d3341e517eb51670cf1b623cc5da2164778cd431 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -41,6 +41,7 @@ int register_android_server_location_GnssLocationProvider(JNIEnv* env); int register_android_server_location_FlpHardwareProvider(JNIEnv* env); int register_android_server_connectivity_Vpn(JNIEnv* env); int register_android_server_hdmi_HdmiCecController(JNIEnv* env); +int register_android_server_tv_TvUinputBridge(JNIEnv* env); int register_android_server_tv_TvInputHal(JNIEnv* env); int register_android_server_PersistentDataBlockService(JNIEnv* env); int register_android_server_Watchdog(JNIEnv* env); @@ -81,6 +82,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_ConsumerIrService(env); register_android_server_BatteryStatsService(env); register_android_server_hdmi_HdmiCecController(env); + register_android_server_tv_TvUinputBridge(env); register_android_server_tv_TvInputHal(env); register_android_server_PersistentDataBlockService(env); register_android_server_Watchdog(env); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 9f2ca591e6efc9cc01730cc5a46b3915845e6497..00b8384152bd8521f7f0a3b4e270bc6a9d20e250 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -90,6 +90,7 @@ import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; import com.android.server.telecom.TelecomLoaderService; import com.android.server.trust.TrustManagerService; +import com.android.server.tv.TvRemoteService; import com.android.server.tv.TvInputManagerService; import com.android.server.twilight.TwilightService; import com.android.server.usage.UsageStatsService; @@ -1111,6 +1112,10 @@ public final class SystemServer { mSystemServiceManager.startService(MediaResourceMonitorService.class); } + if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { + mSystemServiceManager.startService(TvRemoteService.class); + } + if (!disableNonCoreServices) { traceBeginAndSlog("StartMediaRouterService"); try {