Merge "Unbundle RemoteService on TV - part 3" into nyc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
7981e8ff65
@@ -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 \
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
27
media/java/android/media/tv/ITvRemoteProvider.aidl
Normal file
27
media/java/android/media/tv/ITvRemoteProvider.aidl
Normal 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);
|
||||
}
|
||||
33
media/java/android/media/tv/ITvRemoteServiceInput.aidl
Normal file
33
media/java/android/media/tv/ITvRemoteServiceInput.aidl
Normal 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);
|
||||
}
|
||||
48
media/lib/tvremote/Android.mk
Normal file
48
media/lib/tvremote/Android.mk
Normal 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)
|
||||
26
media/lib/tvremote/README.txt
Normal file
26
media/lib/tvremote/README.txt
Normal 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.
|
||||
20
media/lib/tvremote/com.android.media.tv.remoteprovider.xml
Normal file
20
media/lib/tvremote/com.android.media.tv.remoteprovider.xml
Normal 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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
388
services/core/java/com/android/server/tv/TvRemoteService.java
Normal file
388
services/core/java/com/android/server/tv/TvRemoteService.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
139
services/core/java/com/android/server/tv/UinputBridge.java
Normal file
139
services/core/java/com/android/server/tv/UinputBridge.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 \
|
||||
|
||||
113
services/core/jni/com_android_server_tv_TvKeys.h
Normal file
113
services/core/jni/com_android_server_tv_TvKeys.h
Normal 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_
|
||||
308
services/core/jni/com_android_server_tv_TvUinputBridge.cpp
Normal file
308
services/core/jni/com_android_server_tv_TvUinputBridge.cpp
Normal 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
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user