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/ITvInputServiceCallback.aidl \
|
||||||
media/java/android/media/tv/ITvInputSession.aidl \
|
media/java/android/media/tv/ITvInputSession.aidl \
|
||||||
media/java/android/media/tv/ITvInputSessionCallback.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/IMediaBrowserService.aidl \
|
||||||
media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl \
|
media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl \
|
||||||
telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.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_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_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_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_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_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
|
||||||
field public static final java.lang.String BIND_VR_LISTENER_SERVICE = "android.permission.BIND_VR_LISTENER_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 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 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_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 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_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS";
|
||||||
field public static final java.lang.String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_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"
|
<permission android:name="android.permission.BIND_TV_INPUT"
|
||||||
android:protectionLevel="signature|privileged" />
|
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
|
<!-- @SystemApi Allows an application to modify parental controls
|
||||||
<p>Not for use by third-party applications.
|
<p>Not for use by third-party applications.
|
||||||
@hide -->
|
@hide -->
|
||||||
|
|||||||
@@ -2474,4 +2474,8 @@
|
|||||||
-->
|
-->
|
||||||
<integer name="config_externalHardKeyboardBehavior">0</integer>
|
<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>
|
</resources>
|
||||||
|
|||||||
@@ -2574,4 +2574,7 @@
|
|||||||
|
|
||||||
<java-symbol type="dimen" name="input_extract_action_button_width" />
|
<java-symbol type="dimen" name="input_extract_action_button_width" />
|
||||||
<java-symbol type="dimen" name="input_extract_action_button_height" />
|
<java-symbol type="dimen" name="input_extract_action_button_height" />
|
||||||
|
|
||||||
|
<!-- TV Remote Service package -->
|
||||||
|
<java-symbol type="string" name="config_tvRemoteServicePackage" />
|
||||||
</resources>
|
</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_power_PowerManagerService.cpp \
|
||||||
$(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \
|
$(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \
|
||||||
$(LOCAL_REL_DIR)/com_android_server_SystemServer.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_tv_TvInputHal.cpp \
|
||||||
$(LOCAL_REL_DIR)/com_android_server_vr_VrManagerService.cpp \
|
$(LOCAL_REL_DIR)/com_android_server_vr_VrManagerService.cpp \
|
||||||
$(LOCAL_REL_DIR)/com_android_server_UsbDeviceManager.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_location_FlpHardwareProvider(JNIEnv* env);
|
||||||
int register_android_server_connectivity_Vpn(JNIEnv* env);
|
int register_android_server_connectivity_Vpn(JNIEnv* env);
|
||||||
int register_android_server_hdmi_HdmiCecController(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_tv_TvInputHal(JNIEnv* env);
|
||||||
int register_android_server_PersistentDataBlockService(JNIEnv* env);
|
int register_android_server_PersistentDataBlockService(JNIEnv* env);
|
||||||
int register_android_server_Watchdog(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_ConsumerIrService(env);
|
||||||
register_android_server_BatteryStatsService(env);
|
register_android_server_BatteryStatsService(env);
|
||||||
register_android_server_hdmi_HdmiCecController(env);
|
register_android_server_hdmi_HdmiCecController(env);
|
||||||
|
register_android_server_tv_TvUinputBridge(env);
|
||||||
register_android_server_tv_TvInputHal(env);
|
register_android_server_tv_TvInputHal(env);
|
||||||
register_android_server_PersistentDataBlockService(env);
|
register_android_server_PersistentDataBlockService(env);
|
||||||
register_android_server_Watchdog(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.storage.DeviceStorageMonitorService;
|
||||||
import com.android.server.telecom.TelecomLoaderService;
|
import com.android.server.telecom.TelecomLoaderService;
|
||||||
import com.android.server.trust.TrustManagerService;
|
import com.android.server.trust.TrustManagerService;
|
||||||
|
import com.android.server.tv.TvRemoteService;
|
||||||
import com.android.server.tv.TvInputManagerService;
|
import com.android.server.tv.TvInputManagerService;
|
||||||
import com.android.server.twilight.TwilightService;
|
import com.android.server.twilight.TwilightService;
|
||||||
import com.android.server.usage.UsageStatsService;
|
import com.android.server.usage.UsageStatsService;
|
||||||
@@ -1111,6 +1112,10 @@ public final class SystemServer {
|
|||||||
mSystemServiceManager.startService(MediaResourceMonitorService.class);
|
mSystemServiceManager.startService(MediaResourceMonitorService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
|
||||||
|
mSystemServiceManager.startService(TvRemoteService.class);
|
||||||
|
}
|
||||||
|
|
||||||
if (!disableNonCoreServices) {
|
if (!disableNonCoreServices) {
|
||||||
traceBeginAndSlog("StartMediaRouterService");
|
traceBeginAndSlog("StartMediaRouterService");
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user