Merge "Unbundle RemoteService on TV - part 3" into nyc-dev

This commit is contained in:
Sujith Ramakrishnan
2016-04-22 20:59:06 +00:00
committed by Android (Google) Code Review
20 changed files with 2309 additions and 0 deletions

View File

@@ -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 \

View File

@@ -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";

View File

@@ -2279,6 +2279,23 @@
<permission android:name="android.permission.BIND_TV_INPUT"
android:protectionLevel="signature|privileged" />
<!-- @SystemApi
Must be required by a {@link com.android.media.tv.remoteprovider.TvRemoteProvider}
to ensure that only the system can bind to it.
<p>Protection level: signature|privileged
<p>Not for use by third-party applications. </p>
@hide -->
<permission android:name="android.permission.BIND_TV_REMOTE_SERVICE"
android:protectionLevel="signature|privileged" />
<!-- @SystemApi
Must be required for a virtual remote controller for TV.
<p>Protection level: signature|privileged
<p>Not for use by third-party applications. </p>
@hide -->
<permission android:name="android.permission.TV_VIRTUAL_REMOTE_CONTROLLER"
android:protectionLevel="signature|privileged" />
<!-- @SystemApi Allows an application to modify parental controls
<p>Not for use by third-party applications.
@hide -->

View File

@@ -2474,4 +2474,8 @@
-->
<integer name="config_externalHardKeyboardBehavior">0</integer>
<!-- Package of the unbundled tv remote service which can connect to tv
remote provider -->
<string name="config_tvRemoteServicePackage" translatable="false"></string>
</resources>

View File

@@ -2574,4 +2574,7 @@
<java-symbol type="dimen" name="input_extract_action_button_width" />
<java-symbol type="dimen" name="input_extract_action_button_height" />
<!-- TV Remote Service package -->
<java-symbol type="string" name="config_tvRemoteServicePackage" />
</resources>

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -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.

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<permissions>
<library name="com.android.media.tv.remoteprovider"
file="/system/framework/com.android.media.tv.remoteprovider.jar" />
</permissions>

View File

@@ -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.
* <p/>
* This object is not thread safe. It is only intended to be accessed on the
* {@link Context#getMainLooper main looper thread} of an application.
* </p><p>
* 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.
* </p>
*/
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.
* <p>
* This is intended to be used for the onBind() method of a service that implements
* a remote provider service.
* </p>
*
* @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)}
* <p>
* The token should be used for subsequent calls.
* </p>
*
* @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;
}
}
}
}
}

View File

@@ -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.
* <p>
* 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.
* </p>
*/
private static final class RemoteServiceInputProvider extends ITvRemoteServiceInput.Stub {
private final WeakReference<Connection> mConnectionRef;
public RemoteServiceInputProvider(Connection connection) {
mConnectionRef = new WeakReference<Connection>(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);
}
}
}
}

View File

@@ -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<TvRemoteProviderProxy> 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);
}
}

View File

@@ -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.
* <p/>
* 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<IBinder, UinputBridge> mBridgeMap = new ArrayMap();
private Map<IBinder, TvRemoteProviderProxy> mProviderMap = new ArrayMap();
private ArrayList<TvRemoteProviderProxy> 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);
}
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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 \

View File

@@ -0,0 +1,113 @@
#ifndef ANDROIDTVREMOTE_SERVICE_JNI_KEYS_H_
#define ANDROIDTVREMOTE_SERVICE_JNI_KEYS_H_
#include <android/keycodes.h>
#include <linux/input.h>
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_

View File

@@ -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 <android_runtime/AndroidRuntime.h>
#include <ScopedUtfChars.h>
#include <android/keycodes.h>
#include <utils/BitSet.h>
#include <utils/Errors.h>
#include <utils/misc.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include <ctype.h>
#include <linux/input.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <stdint.h>
#include <map>
#include <fcntl.h>
#include <linux/uinput.h>
#include <signal.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/types.h>
// 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<int32_t,int> keysMap;
static std::map<int32_t,int32_t> 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<int,int>::iterator it = keysMap.find(androidKeyCode);
if (it != keysMap.end()) {
return it->second;
}
return KEY_UNKNOWN;
}
static int findSlot(int32_t pointerId) {
std::map<int,int>::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<jlong>(connection);
}
static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) {
NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
delete connection;
}
static void nativeSendTimestamp(JNIEnv* env, jclass clazz, jlong ptr, jlong timestamp) {
NativeConnection* connection = reinterpret_cast<NativeConnection*>(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<NativeConnection*>(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<NativeConnection*>(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<NativeConnection*>(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<NativeConnection*>(ptr);
connection->sendEvent(EV_SYN, SYN_REPORT, 0);
}
static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) {
NativeConnection* connection = reinterpret_cast<NativeConnection*>(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

View File

@@ -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);

View File

@@ -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 {