resolved conflicts for merge of ebe661c4 to master

Change-Id: Ic25dc762ee43654b439de3ec237ead007bee3df4
This commit is contained in:
Jae Seo
2014-06-03 10:03:46 -07:00
30 changed files with 240 additions and 240 deletions

View File

@@ -75,6 +75,8 @@ import android.location.LocationManager;
import android.media.AudioManager;
import android.media.MediaRouter;
import android.media.session.MediaSessionManager;
import android.media.tv.ITvInputManager;
import android.media.tv.TvInputManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.EthernetManager;
@@ -119,8 +121,6 @@ import android.service.fingerprint.FingerprintManager;
import android.service.fingerprint.FingerprintManagerReceiver;
import android.service.fingerprint.FingerprintService;
import android.telephony.TelephonyManager;
import android.tv.ITvInputManager;
import android.tv.TvInputManager;
import android.content.ClipboardManager;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;

View File

@@ -2751,11 +2751,11 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
* {@link android.tv.TvInputManager} for interacting with TV inputs on the
* device.
* {@link android.media.tv.TvInputManager} for interacting with TV inputs
* on the device.
*
* @see #getSystemService
* @see android.tv.TvInputManager
* @see android.media.tv.TvInputManager
*/
public static final String TV_INPUT_SERVICE = "tv_input";

View File

@@ -1,741 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.provider;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.net.Uri;
import android.tv.TvInputService;
import java.util.List;
/**
* <p>
* The contract between the TV provider and applications. Contains definitions for the supported
* URIs and columns.
* </p>
* <h3>Overview</h3>
* <p>
* TvContract defines a basic database of TV content metadata such as channel and program
* information. The information is stored in {@link Channels} and {@link Programs} tables.
* </p>
* <ul>
* <li>A row in the {@link Channels} table represents information about a TV channel. The data
* format can vary greatly from standard to standard or according to service provider, thus
* the columns here are mostly comprised of basic entities that are usually seen to users
* regardless of standard such as channel number and name.</li>
* <li>A row in the {@link Programs} table represents a set of data describing a TV program such
* as program title and start time.</li>
* </ul>
*/
public final class TvContract {
/** The authority for the TV provider. */
public static final String AUTHORITY = "com.android.tv";
private static final String PATH_CHANNEL = "channel";
private static final String PATH_PROGRAM = "program";
private static final String PATH_INPUT = "input";
/**
* An optional query, update or delete URI parameter that allows the caller to specify start
* time (in milliseconds since the epoch) to filter programs.
*
* @hide
*/
public static final String PARAM_START_TIME = "start_time";
/**
* An optional query, update or delete URI parameter that allows the caller to specify end time
* (in milliseconds since the epoch) to filter programs.
*
* @hide
*/
public static final String PARAM_END_TIME = "end_time";
/**
* A query, update or delete URI parameter that allows the caller to operate on all or
* browsable-only channels. If set to "true", the rows that contain non-browsable channels are
* not affected.
*
* @hide
*/
public static final String PARAM_BROWSABLE_ONLY = "browsable_only";
/**
* Builds a URI that points to a specific channel.
*
* @param channelId The ID of the channel to point to.
*/
public static final Uri buildChannelUri(long channelId) {
return ContentUris.withAppendedId(Channels.CONTENT_URI, channelId);
}
/**
* Builds a URI that points to all browsable channels from a given TV input.
*
* @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements
* the given TV input.
*/
public static final Uri buildChannelsUriForInput(ComponentName name) {
return buildChannelsUriForInput(name, true);
}
/**
* Builds a URI that points to all or browsable-only channels from a given TV input.
*
* @param name {@link ComponentName} of the {@link android.tv.TvInputService} that implements
* the given TV input.
* @param browsableOnly If set to {@code true} the URI points to only browsable channels. If set
* to {@code false} the URI points to all channels regardless of whether they are
* browsable or not.
*/
public static final Uri buildChannelsUriForInput(ComponentName name, boolean browsableOnly) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
.appendPath(PATH_INPUT).appendPath(name.getPackageName())
.appendPath(name.getClassName()).appendPath(PATH_CHANNEL)
.appendQueryParameter(PARAM_BROWSABLE_ONLY, String.valueOf(browsableOnly)).build();
}
/**
* Builds a URI that points to a specific program.
*
* @param programId The ID of the program to point to.
*/
public static final Uri buildProgramUri(long programId) {
return ContentUris.withAppendedId(Programs.CONTENT_URI, programId);
}
/**
* Builds a URI that points to all programs on a given channel.
*
* @param channelUri The URI of the channel to return programs for.
*/
public static final Uri buildProgramsUriForChannel(Uri channelUri) {
if (!PATH_CHANNEL.equals(channelUri.getPathSegments().get(0))) {
throw new IllegalArgumentException("Not a channel: " + channelUri);
}
String channelId = String.valueOf(ContentUris.parseId(channelUri));
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
.appendPath(PATH_CHANNEL).appendPath(channelId).appendPath(PATH_PROGRAM).build();
}
/**
* Builds a URI that points to programs on a specific channel whose schedules overlap with the
* given time frame.
*
* @param channelUri The URI of the channel to return programs for.
* @param startTime The start time used to filter programs. The returned programs should have
* {@link Programs#COLUMN_END_TIME_UTC_MILLIS} that is greater than this time.
* @param endTime The end time used to filter programs. The returned programs should have
* {@link Programs#COLUMN_START_TIME_UTC_MILLIS} that is less than this time.
*/
public static final Uri buildProgramsUriForChannel(Uri channelUri, long startTime,
long endTime) {
Uri uri = buildProgramsUriForChannel(channelUri);
return uri.buildUpon().appendQueryParameter(PARAM_START_TIME, String.valueOf(startTime))
.appendQueryParameter(PARAM_END_TIME, String.valueOf(endTime)).build();
}
/**
* Builds a URI that points to a specific program the user watched.
*
* @param watchedProgramId The ID of the watched program to point to.
* @hide
*/
public static final Uri buildWatchedProgramUri(long watchedProgramId) {
return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, watchedProgramId);
}
/**
* Extracts the {@link Channels#COLUMN_PACKAGE_NAME} from a given URI.
*
* @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or
* {@link #buildChannelsUriForInput(ComponentName, boolean)}.
* @hide
*/
public static final String getPackageName(Uri channelsUri) {
final List<String> paths = channelsUri.getPathSegments();
if (paths.size() < 4) {
throw new IllegalArgumentException("Not channels: " + channelsUri);
}
if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) {
throw new IllegalArgumentException("Not channels: " + channelsUri);
}
return paths.get(1);
}
/**
* Extracts the {@link Channels#COLUMN_SERVICE_NAME} from a given URI.
*
* @param channelsUri A URI constructed by {@link #buildChannelsUriForInput(ComponentName)} or
* {@link #buildChannelsUriForInput(ComponentName, boolean)}.
* @hide
*/
public static final String getServiceName(Uri channelsUri) {
final List<String> paths = channelsUri.getPathSegments();
if (paths.size() < 4) {
throw new IllegalArgumentException("Not channels: " + channelsUri);
}
if (!PATH_INPUT.equals(paths.get(0)) || !PATH_CHANNEL.equals(paths.get(3))) {
throw new IllegalArgumentException("Not channels: " + channelsUri);
}
return paths.get(2);
}
/**
* Extracts the {@link Channels#_ID} from a given URI.
*
* @param programsUri A URI constructed by {@link #buildProgramsUriForChannel(Uri)} or
* {@link #buildProgramsUriForChannel(Uri, long, long)}.
* @hide
*/
public static final String getChannelId(Uri programsUri) {
final List<String> paths = programsUri.getPathSegments();
if (paths.size() < 3) {
throw new IllegalArgumentException("Not programs: " + programsUri);
}
if (!PATH_CHANNEL.equals(paths.get(0)) || !PATH_PROGRAM.equals(paths.get(2))) {
throw new IllegalArgumentException("Not programs: " + programsUri);
}
return paths.get(1);
}
private TvContract() {}
/**
* Common base for the tables of TV channels/programs.
*/
public interface BaseTvColumns extends BaseColumns {
/**
* The name of the package that owns a row in each table.
* <p>
* The TV provider fills it in with the name of the package that provides the initial data
* of that row. If the package is later uninstalled, the rows it owns are automatically
* removed from the tables.
* </p><p>
* Type: TEXT
* </p>
*/
public static final String COLUMN_PACKAGE_NAME = "package_name";
}
/** Column definitions for the TV channels table. */
public static final class Channels implements BaseTvColumns {
/** The content:// style URI for this table. */
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"
+ PATH_CHANNEL);
/** The MIME type of a directory of TV channels. */
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.com.android.tv.channels";
/** The MIME type of a single TV channel. */
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.com.android.tv.channels";
/** A generic channel type. */
public static final int TYPE_OTHER = 0x0;
/** The special channel type used for pass-through inputs such as HDMI. */
public static final int TYPE_PASSTHROUGH = 0x00010000;
/** The channel type for DVB-T (terrestrial). */
public static final int TYPE_DVB_T = 0x00020000;
/** The channel type for DVB-T2 (terrestrial). */
public static final int TYPE_DVB_T2 = 0x00020001;
/** The channel type for DVB-S (satellite). */
public static final int TYPE_DVB_S = 0x00020100;
/** The channel type for DVB-S2 (satellite). */
public static final int TYPE_DVB_S2 = 0x00020101;
/** The channel type for DVB-C (cable). */
public static final int TYPE_DVB_C = 0x00020200;
/** The channel type for DVB-C2 (cable). */
public static final int TYPE_DVB_C2 = 0x00020201;
/** The channel type for DVB-H (handheld). */
public static final int TYPE_DVB_H = 0x00020300;
/** The channel type for DVB-SH (satellite). */
public static final int TYPE_DVB_SH = 0x00020400;
/** The channel type for ATSC (terrestrial). */
public static final int TYPE_ATSC_T = 0x00030000;
/** The channel type for ATSC (cable). */
public static final int TYPE_ATSC_C = 0x00030200;
/** The channel type for ATSC-M/H (mobile/handheld). */
public static final int TYPE_ATSC_M_H = 0x00030200;
/** The channel type for ISDB-T (terrestrial). */
public static final int TYPE_ISDB_T = 0x00040000;
/** The channel type for ISDB-Tb (Brazil). */
public static final int TYPE_ISDB_TB = 0x00040100;
/** The channel type for ISDB-S (satellite). */
public static final int TYPE_ISDB_S = 0x00040200;
/** The channel type for ISDB-C (cable). */
public static final int TYPE_ISDB_C = 0x00040300;
/** The channel type for 1seg (handheld). */
public static final int TYPE_1SEG = 0x00040400;
/** The channel type for DTMB (terrestrial). */
public static final int TYPE_DTMB = 0x00050000;
/** The channel type for CMMB (handheld). */
public static final int TYPE_CMMB = 0x00050100;
/** The channel type for T-DMB (terrestrial). */
public static final int TYPE_T_DMB = 0x00060000;
/** The channel type for S-DMB (satellite). */
public static final int TYPE_S_DMB = 0x00060100;
/** A generic service type. */
public static final int SERVICE_TYPE_OTHER = 0x0;
/** The service type for regular TV channels. */
public static final int SERVICE_TYPE_TV = 0x1;
/** The service type for radio channels. */
public static final int SERVICE_TYPE_RADIO = 0x2;
/**
* The name of the {@link TvInputService} subclass that provides this TV channel. This
* should be a fully qualified class name (such as, "com.example.project.TvInputService").
* <p>
* This is a required field.
* </p><p>
* Type: TEXT
* </p>
*/
public static final String COLUMN_SERVICE_NAME = "service_name";
/**
* The predefined type of this TV channel.
* <p>
* This is primarily used to indicate which broadcast standard (e.g. ATSC, DVB or ISDB) the
* current channel conforms to, with an exception being {@link #TYPE_PASSTHROUGH}, which is
* a special channel type used only by pass-through inputs such as HDMI. The value should
* match to one of the followings: {@link #TYPE_OTHER}, {@link #TYPE_PASSTHROUGH},
* {@link #TYPE_DVB_T}, {@link #TYPE_DVB_T2}, {@link #TYPE_DVB_S}, {@link #TYPE_DVB_S2},
* {@link #TYPE_DVB_C}, {@link #TYPE_DVB_C2}, {@link #TYPE_DVB_H}, {@link #TYPE_DVB_SH},
* {@link #TYPE_ATSC_T}, {@link #TYPE_ATSC_C}, {@link #TYPE_ATSC_M_H}, {@link #TYPE_ISDB_T},
* {@link #TYPE_ISDB_TB}, {@link #TYPE_ISDB_S}, {@link #TYPE_ISDB_C} {@link #TYPE_1SEG},
* {@link #TYPE_DTMB}, {@link #TYPE_CMMB}, {@link #TYPE_T_DMB}, {@link #TYPE_S_DMB}
* </p><p>
* This is a required field.
* </p><p>
* Type: INTEGER
* </p>
*/
public static final String COLUMN_TYPE = "type";
/**
* The predefined service type of this TV channel.
* <p>
* This is primarily used to indicate whether the current channel is a regular TV channel or
* a radio-like channel. Use the same coding for {@code service_type} in the underlying
* broadcast standard if it is defined there (e.g. ATSC A/53, ETSI EN 300 468 and ARIB
* STD-B10). Otherwise use one of the followings: {@link #SERVICE_TYPE_OTHER},
* {@link #SERVICE_TYPE_TV}, {@link #SERVICE_TYPE_RADIO}
* </p><p>
* This is a required field.
* </p><p>
* Type: INTEGER
* </p>
*/
public static final String COLUMN_SERVICE_TYPE = "service_type";
/**
* The original network ID of this TV channel.
* <p>
* This is used to identify the originating delivery system, if applicable. Use the same
* coding for {@code origianal_network_id} in the underlying broadcast standard if it is
* defined there (e.g. ETSI EN 300 468/TR 101 211 and ARIB STD-B10). If channels cannot be
* globally identified by 2-tuple {{@link #COLUMN_TRANSPORT_STREAM_ID},
* {@link #COLUMN_SERVICE_ID}}, one must carefully assign a value to this field to form a
* unique 3-tuple identification {{@link #COLUMN_ORIGINAL_NETWORK_ID},
* {@link #COLUMN_TRANSPORT_STREAM_ID}, {@link #COLUMN_SERVICE_ID}} for its channels.
* </p><p>
* This is a required field if the channel cannot be uniquely identified by a 2-tuple
* {{@link #COLUMN_TRANSPORT_STREAM_ID}, {@link #COLUMN_SERVICE_ID}}.
* </p><p>
* Type: INTEGER
* </p>
*/
public static final String COLUMN_ORIGINAL_NETWORK_ID = "original_network_id";
/**
* The transport stream ID of this channel.
* <p>
* This is used to identify the Transport Stream that contains the current channel from any
* other multiplex within a network, if applicable. Use the same coding for
* {@code transport_stream_id} defined in ISO/IEC 13818-1 if the channel is transmitted via
* the MPEG Transport Stream as is the case for many digital broadcast standards.
* </p><p>
* This is a required field if the current channel is transmitted via the MPEG Transport
* Stream.
* </p><p>
* Type: INTEGER
* </p>
*/
public static final String COLUMN_TRANSPORT_STREAM_ID = "transport_stream_id";
/**
* The service ID of this channel.
* <p>
* This is used to identify the current service (roughly equivalent to channel) from any
* other service within the Transport Stream, if applicable. Use the same coding for
* {@code service_id} in the underlying broadcast standard if it is defined there (e.g. ETSI
* EN 300 468 and ARIB STD-B10) or {@code program_number} (which usually has the same value
* as {@code service_id}) in ISO/IEC 13818-1 if the channel is transmitted via the MPEG
* Transport Stream.
* </p><p>
* This is a required field if the current channel is transmitted via the MPEG Transport
* Stream.
* </p><p>
* Type: INTEGER
* </p>
*/
public static final String COLUMN_SERVICE_ID = "service_id";
/**
* The channel number that is displayed to the user.
* <p>
* The format can vary depending on broadcast standard and product specification.
* </p><p>
* Type: TEXT
* </p>
*/
public static final String COLUMN_DISPLAY_NUMBER = "display_number";
/**
* The channel name that is displayed to the user.
* <p>
* A call sign is a good candidate to use for this purpose but any name that helps the user
* recognize the current channel will be enough. Can also be empty depending on broadcast
* standard.
* </p><p>
* Type: TEXT
* </p>
*/
public static final String COLUMN_DISPLAY_NAME = "display_name";
/**
* The description of this TV channel.
* <p>
* Can be empty initially.
* </p><p>
* Type: TEXT
* </p>
*/
public static final String COLUMN_DESCRIPTION = "description";
/**
* The flag indicating whether this TV channel is browsable or not.
* <p>
* A value of 1 indicates the channel is included in the channel list that applications use
* to browse channels, a value of 0 indicates the channel is not included in the list. If
* not specified, this value is set to 1 (browsable) by default.
* </p><p>
* Type: INTEGER (boolean)
* </p>
*/
public static final String COLUMN_BROWSABLE = "browsable";
/**
* The flag indicating whether this TV channel is searchable or not.
* <p>
* In some regions, it is not allowed to surface search results for a given channel without
* broadcaster's consent. This is used to impose such restriction. A value of 1 indicates
* the channel is searchable and can be included in search results, a value of 0 indicates
* the channel and its TV programs are hidden from search. If not specified, this value is
* set to 1 (searchable) by default.
* </p>
* <p>
* Type: INTEGER (boolean)
* </p>
*/
public static final String COLUMN_SEARCHABLE = "searchable";
/**
* The flag indicating whether this TV channel is locked or not.
* <p>
* This is primarily used for alternative parental control to prevent unauthorized users
* from watching the current channel regardless of the content rating. A value of 1
* indicates the channel is locked and the user is required to enter passcode to unlock it
* in order to watch the current program from the channel, a value of 0 indicates the
* channel is not locked thus the user is not prompted to enter passcode If not specified,
* this value is set to 0 (not locked) by default.
* </p><p>
* Type: INTEGER (boolean)
* </p>
*/
public static final String COLUMN_LOCKED = "locked";
/**
* Generic data used by individual TV input services.
* <p>
* Type: BLOB
* </p>
*/
public static final String COLUMN_DATA = "data";
/**
* The version number of this row entry used by TV input services.
* <p>
* This is best used by sync adapters to identify the rows to update. The number can be
* defined by individual TV input services. One may assign the same value as
* {@code version_number} that appears in ETSI EN 300 468 or ATSC A/65, if the data are
* coming from a TV broadcast.
* </p><p>
* Type: INTEGER
* </p>
*/
public static final String COLUMN_VERSION_NUMBER = "version_number";
private Channels() {}
}
/** Column definitions for the TV programs table. */
public static final class Programs implements BaseTvColumns {
/** The content:// style URI for this table. */
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"
+ PATH_PROGRAM);
/** The MIME type of a directory of TV programs. */
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.com.android.tv.programs";
/** The MIME type of a single TV program. */
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.com.android.tv.programs";
/**
* The ID of the TV channel that contains this TV program.
* <p>
* This is a part of the channel URI and matches to {@link BaseColumns#_ID}.
* </p><p>
* Type: INTEGER (long)
* </p>
*/
public static final String COLUMN_CHANNEL_ID = "channel_id";
/**
* The title of this TV program.
* <p>
* Type: TEXT
* </p>
**/
public static final String COLUMN_TITLE = "title";
/**
* The start time of this TV program, in milliseconds since the epoch.
* <p>
* Type: INTEGER (long)
* </p>
*/
public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
/**
* The end time of this TV program, in milliseconds since the epoch.
* <p>
* Type: INTEGER (long)
* </p>
*/
public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
/**
* The comma-separated genre string of this TV program.
* <p>
* Use the same language appeared in the underlying broadcast standard, if applicable. (For
* example, one can refer to the genre strings used in Genre Descriptor of ATSC A/65 or
* Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, use one of the
* following genres:
* <ul>
* <li>Family/Kids</li>
* <li>Sports</li>
* <li>Shopping</li>
* <li>Movies</li>
* <li>Comedy</li>
* <li>Travel</li>
* <li>Drama</li>
* <li>Education</li>
* <li>Animal/Wildlife</li>
* <li>News</li>
* <li>Gaming</li>
* <li>Others</li>
* </ul>
* </p><p>
* Type: TEXT
* </p>
*/
public static final String COLUMN_GENRE = "genre";
/**
* The description of this TV program that is displayed to the user by default.
* <p>
* The maximum length of this field is 256 characters.
* </p><p>
* Type: TEXT
* </p>
*/
public static final String COLUMN_DESCRIPTION = "description";
/**
* The detailed, lengthy description of this TV program that is displayed only when the user
* wants to see more information.
* <p>
* TV input services should leave this field empty if they have no additional
* details beyond {@link #COLUMN_DESCRIPTION}.
* </p><p>
* Type: TEXT
* </p>
*/
public static final String COLUMN_LONG_DESCRIPTION = "long_description";
/**
* The comma-separated audio languages of this TV program.
* <p>
* This is used to describe available audio languages included in the program. Use
* 3-character language code as specified by ISO 639-2.
* </p><p>
* Type: TEXT
* </p>
*/
public static final String COLUMN_AUDIO_LANGUAGE = "audio_language";
/**
* Generic data used by TV input services.
* <p>
* Type: BLOB
* </p>
*/
public static final String COLUMN_DATA = "data";
/**
* The version number of this row entry used by TV input services.
* <p>
* This is best used by sync adapters to identify the rows to update. The number can be
* defined by individual TV input services. One may assign the same value as
* {@code version_number} in ETSI EN 300 468 or ATSC A/65, if the data are coming from a TV
* broadcast.
* </p><p>
* Type: INTEGER
* </p>
*/
public static final String COLUMN_VERSION_NUMBER = "version_number";
private Programs() {}
}
/**
* Column definitions for the TV programs that the user watched. Applications do not have access
* to this table.
*
* @hide
*/
public static final class WatchedPrograms implements BaseColumns {
/** The content:// style URI for this table. */
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/watched_program");
/** The MIME type of a directory of watched programs. */
public static final String CONTENT_TYPE =
"vnd.android.cursor.dir/vnd.com.android.tv.watched_programs";
/** The MIME type of a single item in this table. */
public static final String CONTENT_ITEM_TYPE =
"vnd.android.cursor.item/vnd.com.android.tv.watched_programs";
/**
* The UTC time that the user started watching this TV program, in milliseconds since the
* epoch.
* <p>
* Type: INTEGER (long)
* </p>
*/
public static final String COLUMN_WATCH_START_TIME_UTC_MILLIS =
"watch_start_time_utc_millis";
/**
* The UTC time that the user stopped watching this TV program, in milliseconds since the
* epoch.
* <p>
* Type: INTEGER (long)
* </p>
*/
public static final String COLUMN_WATCH_END_TIME_UTC_MILLIS = "watch_end_time_utc_millis";
/**
* The channel ID that contains this TV program.
* <p>
* Type: INTEGER (long)
* </p>
*/
public static final String COLUMN_CHANNEL_ID = "channel_id";
/**
* The title of this TV program.
* <p>
* Type: TEXT
* </p>
*/
public static final String COLUMN_TITLE = "title";
/**
* The start time of this TV program, in milliseconds since the epoch.
* <p>
* Type: INTEGER (long)
* </p>
*/
public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
/**
* The end time of this TV program, in milliseconds since the epoch.
* <p>
* Type: INTEGER (long)
* </p>
*/
public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
/**
* The description of this TV program.
* <p>
* Type: TEXT
* </p>
*/
public static final String COLUMN_DESCRIPTION = "description";
private WatchedPrograms() {}
}
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.content.ComponentName;
import android.os.Bundle;
import android.tv.ITvInputSession;
import android.view.InputChannel;
/**
* Interface a client of the ITvInputManager implements, to identify itself and receive information
* about changes to the state of each TV input service.
* @hide
*/
oneway interface ITvInputClient {
void onSessionCreated(in String inputId, IBinder token, in InputChannel channel, int seq);
void onAvailabilityChanged(in String inputId, boolean isAvailable);
void onSessionReleased(int seq);
void onSessionEvent(in String name, in Bundle args, int seq);
void onVideoSizeChanged(int width, int height, int seq);
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.tv.TvStreamConfig;
import android.view.KeyEvent;
import android.view.Surface;
/**
* TvInputService representing a physical port should connect to HAL through this interface.
* Framework will take care of communication among system services including TvInputManagerService,
* HdmiControlService, AudioService, etc.
*
* @hide
*/
interface ITvInputHardware {
/**
* Make the input render on the surface according to the config. In case of HDMI, this will
* trigger CEC commands for adjusting active HDMI source. Returns true on success.
*/
boolean setSurface(in Surface surface, in TvStreamConfig config);
/**
* Set volume for this stream via AudioGain. (TBD)
*/
void setVolume(float volume);
/**
* Dispatch key event to HDMI service. The events would be automatically converted to
* HDMI CEC commands. If the hardware is not representing an HDMI port, this method will fail.
*/
boolean dispatchKeyEventToHdmi(in KeyEvent event);
}

View File

@@ -1,27 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.tv.TvStreamConfig;
/**
* @hide
*/
oneway interface ITvInputHardwareCallback {
void onReleased();
void onStreamConfigChanged(in TvStreamConfig[] configs);
}

View File

@@ -1,58 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.content.ComponentName;
import android.graphics.Rect;
import android.net.Uri;
import android.tv.ITvInputHardware;
import android.tv.ITvInputHardwareCallback;
import android.tv.ITvInputClient;
import android.tv.TvInputHardwareInfo;
import android.tv.TvInputInfo;
import android.view.Surface;
/**
* Interface to the TV input manager service.
* @hide
*/
interface ITvInputManager {
List<TvInputInfo> getTvInputList(int userId);
boolean getAvailability(in ITvInputClient client, in String inputId, int userId);
void registerCallback(in ITvInputClient client, in String inputId, int userId);
void unregisterCallback(in ITvInputClient client, in String inputId, int userId);
void createSession(in ITvInputClient client, in String inputId, int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void setVolume(in IBinder sessionToken, float volume, int userId);
void tune(in IBinder sessionToken, in Uri channelUri, int userId);
void createOverlayView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
int userId);
void relayoutOverlayView(in IBinder sessionToken, in Rect frame, int userId);
void removeOverlayView(in IBinder sessionToken, int userId);
// For TV input hardware binding
List<TvInputHardwareInfo> getHardwareList();
ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback,
int userId);
void releaseTvInputHardware(int deviceId, in ITvInputHardware hardware, int userId);
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.tv.ITvInputServiceCallback;
import android.tv.ITvInputSessionCallback;
import android.view.InputChannel;
/**
* Top-level interface to a TV input component (implemented in a Service).
* @hide
*/
oneway interface ITvInputService {
void registerCallback(ITvInputServiceCallback callback);
void unregisterCallback(in ITvInputServiceCallback callback);
void createSession(in InputChannel channel, ITvInputSessionCallback callback);
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.content.ComponentName;
/**
* Helper interface for ITvInputService to allow the TV input to notify the client when its status
* has been changed.
* @hide
*/
oneway interface ITvInputServiceCallback {
void onAvailabilityChanged(in String inputId, boolean isAvailable);
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.graphics.Rect;
import android.net.Uri;
import android.view.Surface;
/**
* Sub-interface of ITvInputService which is created per session and has its own context.
* @hide
*/
oneway interface ITvInputSession {
void release();
void setSurface(in Surface surface);
// TODO: Remove this once it becomes irrelevant for applications to handle audio focus. The plan
// is to introduce some new concepts that will solve a number of problems in audio policy today.
void setVolume(float volume);
void tune(in Uri channelUri);
void createOverlayView(in IBinder windowToken, in Rect frame);
void relayoutOverlayView(in Rect frame);
void removeOverlayView();
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.os.Bundle;
import android.tv.ITvInputSession;
/**
* Helper interface for ITvInputSession to allow the TV input to notify the system service when a
* new session has been created.
* @hide
*/
oneway interface ITvInputSessionCallback {
void onSessionCreated(ITvInputSession session);
void onSessionEvent(in String name, in Bundle args);
void onVideoSizeChanged(int width, int height);
}

View File

@@ -1,179 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.content.Context;
import android.graphics.Rect;
import android.net.Uri;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.tv.TvInputManager.Session;
import android.tv.TvInputService.TvInputSessionImpl;
import android.util.Log;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
/**
* Implements the internal ITvInputSession interface to convert incoming calls on to it back to
* calls on the public TvInputSession interface, scheduling them on the main thread of the process.
*
* @hide
*/
public class ITvInputSessionWrapper extends ITvInputSession.Stub implements HandlerCaller.Callback {
private static final String TAG = "TvInputSessionWrapper";
private static final int DO_RELEASE = 1;
private static final int DO_SET_SURFACE = 2;
private static final int DO_SET_VOLUME = 3;
private static final int DO_TUNE = 4;
private static final int DO_CREATE_OVERLAY_VIEW = 5;
private static final int DO_RELAYOUT_OVERLAY_VIEW = 6;
private static final int DO_REMOVE_OVERLAY_VIEW = 7;
private final HandlerCaller mCaller;
private TvInputSessionImpl mTvInputSessionImpl;
private InputChannel mChannel;
private TvInputEventReceiver mReceiver;
public ITvInputSessionWrapper(Context context, TvInputSessionImpl sessionImpl,
InputChannel channel) {
mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
mTvInputSessionImpl = sessionImpl;
mChannel = channel;
if (channel != null) {
mReceiver = new TvInputEventReceiver(channel, context.getMainLooper());
}
}
@Override
public void executeMessage(Message msg) {
if (mTvInputSessionImpl == null) {
return;
}
switch (msg.what) {
case DO_RELEASE: {
mTvInputSessionImpl.release();
mTvInputSessionImpl = null;
if (mReceiver != null) {
mReceiver.dispose();
mReceiver = null;
}
if (mChannel != null) {
mChannel.dispose();
mChannel = null;
}
return;
}
case DO_SET_SURFACE: {
mTvInputSessionImpl.setSurface((Surface) msg.obj);
return;
}
case DO_SET_VOLUME: {
mTvInputSessionImpl.setVolume((Float) msg.obj);
return;
}
case DO_TUNE: {
mTvInputSessionImpl.tune((Uri) msg.obj);
return;
}
case DO_CREATE_OVERLAY_VIEW: {
SomeArgs args = (SomeArgs) msg.obj;
mTvInputSessionImpl.createOverlayView((IBinder) args.arg1, (Rect) args.arg2);
args.recycle();
return;
}
case DO_RELAYOUT_OVERLAY_VIEW: {
mTvInputSessionImpl.relayoutOverlayView((Rect) msg.obj);
return;
}
case DO_REMOVE_OVERLAY_VIEW: {
mTvInputSessionImpl.removeOverlayView(true);
return;
}
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
return;
}
}
}
@Override
public void release() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
}
@Override
public void setSurface(Surface surface) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
}
@Override
public final void setVolume(float volume) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VOLUME, volume));
}
@Override
public void tune(Uri channelUri) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_TUNE, channelUri));
}
@Override
public void createOverlayView(IBinder windowToken, Rect frame) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CREATE_OVERLAY_VIEW, windowToken,
frame));
}
@Override
public void relayoutOverlayView(Rect frame) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_RELAYOUT_OVERLAY_VIEW, frame));
}
@Override
public void removeOverlayView() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_OVERLAY_VIEW));
}
private final class TvInputEventReceiver extends InputEventReceiver {
public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEvent(InputEvent event) {
if (mTvInputSessionImpl == null) {
// The session has been finished.
finishInputEvent(event, false);
return;
}
int handled = mTvInputSessionImpl.dispatchInputEvent(event, this);
if (handled != Session.DISPATCH_IN_PROGRESS) {
finishInputEvent(event, handled == Session.DISPATCH_HANDLED);
}
}
}
}

View File

@@ -1,20 +0,0 @@
/*
*
* Copyright 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
parcelable TvInputHardwareInfo;

View File

@@ -1,93 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
/**
* Simple container for information about TV input hardware.
* Not for third-party developers.
*
* @hide
*/
public final class TvInputHardwareInfo implements Parcelable {
static final String TAG = "TvInputHardwareInfo";
// Match hardware/libhardware/include/hardware/tv_input.h
public static final int TV_INPUT_TYPE_HDMI = 1;
public static final int TV_INPUT_TYPE_BUILT_IN_TUNER = 2;
public static final int TV_INPUT_TYPE_PASSTHROUGH = 3;
public static final Parcelable.Creator<TvInputHardwareInfo> CREATOR =
new Parcelable.Creator<TvInputHardwareInfo>() {
@Override
public TvInputHardwareInfo createFromParcel(Parcel source) {
try {
TvInputHardwareInfo info = new TvInputHardwareInfo();
info.readFromParcel(source);
return info;
} catch (Exception e) {
Log.e(TAG, "Exception creating TvInputHardwareInfo from parcel", e);
return null;
}
}
@Override
public TvInputHardwareInfo[] newArray(int size) {
return new TvInputHardwareInfo[size];
}
};
private int mDeviceId;
private int mType;
// TODO: Add audio port & audio address for audio service.
// TODO: Add HDMI handle for HDMI service.
public TvInputHardwareInfo() { }
public TvInputHardwareInfo(int deviceId, int type) {
mDeviceId = deviceId;
mType = type;
}
public int getDeviceId() {
return mDeviceId;
}
public int getType() {
return mType;
}
// Parcelable
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mDeviceId);
dest.writeInt(mType);
}
public void readFromParcel(Parcel source) {
mDeviceId = source.readInt();
mType = source.readInt();
}
}

View File

@@ -1,19 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
parcelable TvInputInfo;

View File

@@ -1,163 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Parcel;
import android.os.Parcelable;
/**
* This class is used to specify meta information of a TV input.
*/
public final class TvInputInfo implements Parcelable {
private final ResolveInfo mService;
private final String mId;
/**
* Constructor.
*
* @param service The ResolveInfo returned from the package manager about this TV input service.
* @hide
*/
public TvInputInfo(ResolveInfo service) {
mService = service;
ServiceInfo si = service.serviceInfo;
mId = generateInputIdForComponentName(new ComponentName(si.packageName, si.name));
}
/**
* Returns a unique ID for this TV input. The ID is generated from the package and class name
* implementing the TV input service.
*/
public String getId() {
return mId;
}
/**
* Returns the .apk package that implements this TV input service.
*/
public String getPackageName() {
return mService.serviceInfo.packageName;
}
/**
* Returns the class name of the service component that implements this TV input service.
*/
public String getServiceName() {
return mService.serviceInfo.name;
}
/**
* Returns the component of the service that implements this TV input.
*/
public ComponentName getComponent() {
return new ComponentName(mService.serviceInfo.packageName, mService.serviceInfo.name);
}
/**
* Loads the user-displayed label for this TV input service.
*
* @param pm Supplies a PackageManager used to load the TV input's resources.
* @return a CharSequence containing the TV input's label. If the TV input does not have
* a label, its name is returned.
*/
public CharSequence loadLabel(PackageManager pm) {
return mService.loadLabel(pm);
}
@Override
public int describeContents() {
return 0;
}
@Override
public int hashCode() {
return mId.hashCode();
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof TvInputInfo)) {
return false;
}
TvInputInfo obj = (TvInputInfo) o;
return mId.equals(obj.mId)
&& mService.serviceInfo.packageName.equals(obj.mService.serviceInfo.packageName)
&& mService.serviceInfo.name.equals(obj.mService.serviceInfo.name);
}
@Override
public String toString() {
return "TvInputInfo{id=" + mId
+ ", pkg=" + mService.serviceInfo.packageName
+ ", service=" + mService.serviceInfo.name + "}";
}
/**
* Used to package this object into a {@link Parcel}.
*
* @param dest The {@link Parcel} to be written.
* @param flags The flags used for parceling.
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mId);
mService.writeToParcel(dest, flags);
}
/**
* Used to generate an input id from a ComponentName.
*
* @param name the component name for generating an input id.
* @return the generated input id for the given {@code name}.
* @hide
*/
public static final String generateInputIdForComponentName(ComponentName name) {
return name.flattenToShortString();
}
/**
* Used to make this class parcelable.
*
* @hide
*/
public static final Parcelable.Creator<TvInputInfo> CREATOR =
new Parcelable.Creator<TvInputInfo>() {
@Override
public TvInputInfo createFromParcel(Parcel in) {
return new TvInputInfo(in);
}
@Override
public TvInputInfo[] newArray(int size) {
return new TvInputInfo[size];
}
};
private TvInputInfo(Parcel in) {
mId = in.readString();
mService = ResolveInfo.CREATOR.createFromParcel(in);
}
}

View File

@@ -1,850 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.util.Pools.Pool;
import android.util.Pools.SimplePool;
import android.util.SparseArray;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.Surface;
import android.view.View;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Central system API to the overall TV input framework (TIF) architecture, which arbitrates
* interaction between applications and the selected TV inputs.
*/
public final class TvInputManager {
private static final String TAG = "TvInputManager";
private final ITvInputManager mService;
// A mapping from an input to the list of its TvInputListenerRecords.
private final Map<String, List<TvInputListenerRecord>> mTvInputListenerRecordsMap =
new HashMap<String, List<TvInputListenerRecord>>();
// A mapping from the sequence number of a session to its SessionCallbackRecord.
private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
new SparseArray<SessionCallbackRecord>();
// A sequence number for the next session to be created. Should be protected by a lock
// {@code mSessionCallbackRecordMap}.
private int mNextSeq;
private final ITvInputClient mClient;
private final int mUserId;
/**
* Interface used to receive the created session.
*/
public abstract static class SessionCallback {
/**
* This is called after {@link TvInputManager#createSession} has been processed.
*
* @param session A {@link TvInputManager.Session} instance created. This can be
* {@code null} if the creation request failed.
*/
public void onSessionCreated(Session session) {
}
/**
* This is called when {@link TvInputManager.Session} is released.
* This typically happens when the process hosting the session has crashed or been killed.
*
* @param session A {@link TvInputManager.Session} instance released.
*/
public void onSessionReleased(Session session) {
}
/**
* This is called at the beginning of the playback of a channel and later when the size of
* the video has been changed.
*
* @param session A {@link TvInputManager.Session} associated with this callback
* @param width the width of the video
* @param height the height of the video
* @hide
*/
public void onVideoSizeChanged(Session session, int width, int height) {
}
/**
* This is called when a custom event has been sent from this session.
*
* @param session A {@link TvInputManager.Session} associated with this callback
* @param eventType The type of the event.
* @param eventArgs Optional arguments of the event.
* @hide
*/
public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
}
}
private static final class SessionCallbackRecord {
private final SessionCallback mSessionCallback;
private final Handler mHandler;
private Session mSession;
public SessionCallbackRecord(SessionCallback sessionCallback,
Handler handler) {
mSessionCallback = sessionCallback;
mHandler = handler;
}
public void postSessionCreated(final Session session) {
mSession = session;
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onSessionCreated(session);
}
});
}
public void postSessionReleased() {
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onSessionReleased(mSession);
}
});
}
public void postVideoSizeChanged(final int width, final int height) {
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onVideoSizeChanged(mSession, width, height);
}
});
}
public void postSessionEvent(final String eventType, final Bundle eventArgs) {
mHandler.post(new Runnable() {
@Override
public void run() {
mSessionCallback.onSessionEvent(mSession, eventType, eventArgs);
}
});
}
}
/**
* Interface used to monitor status of the TV input.
*/
public abstract static class TvInputListener {
/**
* This is called when the availability status of a given TV input is changed.
*
* @param inputId the id of the TV input.
* @param isAvailable {@code true} if the given TV input is available to show TV programs.
* {@code false} otherwise.
*/
public void onAvailabilityChanged(String inputId, boolean isAvailable) {
}
}
private static final class TvInputListenerRecord {
private final TvInputListener mListener;
private final Handler mHandler;
public TvInputListenerRecord(TvInputListener listener, Handler handler) {
mListener = listener;
mHandler = handler;
}
public TvInputListener getListener() {
return mListener;
}
public void postAvailabilityChanged(final String inputId, final boolean isAvailable) {
mHandler.post(new Runnable() {
@Override
public void run() {
mListener.onAvailabilityChanged(inputId, isAvailable);
}
});
}
}
/**
* @hide
*/
public TvInputManager(ITvInputManager service, int userId) {
mService = service;
mUserId = userId;
mClient = new ITvInputClient.Stub() {
@Override
public void onSessionCreated(String inputId, IBinder token, InputChannel channel,
int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
Log.e(TAG, "Callback not found for " + token);
return;
}
Session session = null;
if (token != null) {
session = new Session(token, channel, mService, mUserId, seq,
mSessionCallbackRecordMap);
}
record.postSessionCreated(session);
}
}
@Override
public void onSessionReleased(int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
mSessionCallbackRecordMap.delete(seq);
if (record == null) {
Log.e(TAG, "Callback not found for seq:" + seq);
return;
}
record.mSession.releaseInternal();
record.postSessionReleased();
}
}
@Override
public void onVideoSizeChanged(int width, int height, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
Log.e(TAG, "Callback not found for seq " + seq);
return;
}
record.postVideoSizeChanged(width, height);
}
}
@Override
public void onSessionEvent(String eventType, Bundle eventArgs, int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
if (record == null) {
Log.e(TAG, "Callback not found for seq " + seq);
return;
}
record.postSessionEvent(eventType, eventArgs);
}
}
@Override
public void onAvailabilityChanged(String inputId, boolean isAvailable) {
synchronized (mTvInputListenerRecordsMap) {
List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
if (records == null) {
// Silently ignore - no listener is registered yet.
return;
}
int recordsCount = records.size();
for (int i = 0; i < recordsCount; i++) {
records.get(i).postAvailabilityChanged(inputId, isAvailable);
}
}
}
};
}
/**
* Returns the complete list of TV inputs on the system.
*
* @return List of {@link TvInputInfo} for each TV input that describes its meta information.
*/
public List<TvInputInfo> getTvInputList() {
try {
return mService.getTvInputList(mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Returns the availability of a given TV input.
*
* @param inputId the id of the TV input.
* @throws IllegalArgumentException if the argument is {@code null}.
* @throws IllegalStateException If there is no {@link TvInputListener} registered on the given
* TV input.
*/
public boolean getAvailability(String inputId) {
if (inputId == null) {
throw new IllegalArgumentException("id cannot be null");
}
synchronized (mTvInputListenerRecordsMap) {
List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
if (records == null || records.size() == 0) {
throw new IllegalStateException("At least one listener should be registered.");
}
}
try {
return mService.getAvailability(mClient, inputId, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Registers a {@link TvInputListener} for a given TV input.
*
* @param inputId the id of the TV input.
* @param listener a listener used to monitor status of the given TV input.
* @param handler a {@link Handler} that the status change will be delivered to.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
*/
public void registerListener(String inputId, TvInputListener listener, Handler handler) {
if (inputId == null) {
throw new IllegalArgumentException("id cannot be null");
}
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
if (handler == null) {
throw new IllegalArgumentException("handler cannot be null");
}
synchronized (mTvInputListenerRecordsMap) {
List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
if (records == null) {
records = new ArrayList<TvInputListenerRecord>();
mTvInputListenerRecordsMap.put(inputId, records);
try {
mService.registerCallback(mClient, inputId, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
records.add(new TvInputListenerRecord(listener, handler));
}
}
/**
* Unregisters the existing {@link TvInputListener} for a given TV input.
*
* @param inputId the id of the TV input.
* @param listener the existing listener to remove for the given TV input.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
*/
public void unregisterListener(String inputId, final TvInputListener listener) {
if (inputId == null) {
throw new IllegalArgumentException("id cannot be null");
}
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
synchronized (mTvInputListenerRecordsMap) {
List<TvInputListenerRecord> records = mTvInputListenerRecordsMap.get(inputId);
if (records == null) {
Log.e(TAG, "No listener found for " + inputId);
return;
}
for (Iterator<TvInputListenerRecord> it = records.iterator(); it.hasNext();) {
TvInputListenerRecord record = it.next();
if (record.getListener() == listener) {
it.remove();
}
}
if (records.isEmpty()) {
try {
mService.unregisterCallback(mClient, inputId, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
} finally {
mTvInputListenerRecordsMap.remove(inputId);
}
}
}
}
/**
* Creates a {@link Session} for a given TV input.
* <p>
* The number of sessions that can be created at the same time is limited by the capability of
* the given TV input.
* </p>
*
* @param inputId the id of the TV input.
* @param callback a callback used to receive the created session.
* @param handler a {@link Handler} that the session creation will be delivered to.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
*/
public void createSession(String inputId, final SessionCallback callback,
Handler handler) {
if (inputId == null) {
throw new IllegalArgumentException("id cannot be null");
}
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
if (handler == null) {
throw new IllegalArgumentException("handler cannot be null");
}
SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
synchronized (mSessionCallbackRecordMap) {
int seq = mNextSeq++;
mSessionCallbackRecordMap.put(seq, record);
try {
mService.createSession(mClient, inputId, seq, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
}
/** The Session provides the per-session functionality of TV inputs. */
public static final class Session {
static final int DISPATCH_IN_PROGRESS = -1;
static final int DISPATCH_NOT_HANDLED = 0;
static final int DISPATCH_HANDLED = 1;
private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
private final ITvInputManager mService;
private final int mUserId;
private final int mSeq;
// For scheduling input event handling on the main thread. This also serves as a lock to
// protect pending input events and the input channel.
private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
private final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20);
private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20);
private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
private IBinder mToken;
private TvInputEventSender mSender;
private InputChannel mChannel;
/** @hide */
private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
mToken = token;
mChannel = channel;
mService = service;
mUserId = userId;
mSeq = seq;
mSessionCallbackRecordMap = sessionCallbackRecordMap;
}
/**
* Releases this session.
*
* @throws IllegalStateException if the session has been already released.
*/
public void release() {
if (mToken == null) {
throw new IllegalStateException("the session has been already released");
}
try {
mService.releaseSession(mToken, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
releaseInternal();
}
/**
* Sets the {@link android.view.Surface} for this session.
*
* @param surface A {@link android.view.Surface} used to render video.
* @throws IllegalStateException if the session has been already released.
* @hide
*/
public void setSurface(Surface surface) {
if (mToken == null) {
throw new IllegalStateException("the session has been already released");
}
// surface can be null.
try {
mService.setSurface(mToken, surface, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Sets the relative volume of this session to handle a change of audio focus.
*
* @param volume A volume value between 0.0f to 1.0f.
* @throws IllegalArgumentException if the volume value is out of range.
* @throws IllegalStateException if the session has been already released.
*/
public void setVolume(float volume) {
if (mToken == null) {
throw new IllegalStateException("the session has been already released");
}
try {
if (volume < 0.0f || volume > 1.0f) {
throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
}
mService.setVolume(mToken, volume, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Tunes to a given channel.
*
* @param channelUri The URI of a channel.
* @throws IllegalArgumentException if the argument is {@code null}.
* @throws IllegalStateException if the session has been already released.
*/
public void tune(Uri channelUri) {
if (channelUri == null) {
throw new IllegalArgumentException("channelUri cannot be null");
}
if (mToken == null) {
throw new IllegalStateException("the session has been already released");
}
try {
mService.tune(mToken, channelUri, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView}
* should be called whenever the layout of its containing view is changed.
* {@link #removeOverlayView()} should be called to remove the overlay view.
* Since a session can have only one overlay view, this method should be called only once
* or it can be called again after calling {@link #removeOverlayView()}.
*
* @param view A view playing TV.
* @param frame A position of the overlay view.
* @throws IllegalArgumentException if any of the arguments is {@code null}.
* @throws IllegalStateException if {@code view} is not attached to a window or
* if the session has been already released.
*/
void createOverlayView(View view, Rect frame) {
if (view == null) {
throw new IllegalArgumentException("view cannot be null");
}
if (frame == null) {
throw new IllegalArgumentException("frame cannot be null");
}
if (view.getWindowToken() == null) {
throw new IllegalStateException("view must be attached to a window");
}
if (mToken == null) {
throw new IllegalStateException("the session has been already released");
}
try {
mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Relayouts the current overlay view.
*
* @param frame A new position of the overlay view.
* @throws IllegalArgumentException if the arguments is {@code null}.
* @throws IllegalStateException if the session has been already released.
*/
void relayoutOverlayView(Rect frame) {
if (frame == null) {
throw new IllegalArgumentException("frame cannot be null");
}
if (mToken == null) {
throw new IllegalStateException("the session has been already released");
}
try {
mService.relayoutOverlayView(mToken, frame, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Removes the current overlay view.
*
* @throws IllegalStateException if the session has been already released.
*/
void removeOverlayView() {
if (mToken == null) {
throw new IllegalStateException("the session has been already released");
}
try {
mService.removeOverlayView(mToken, mUserId);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
/**
* Dispatches an input event to this session.
*
* @param event {@link InputEvent} to dispatch.
* @param token A token used to identify the input event later in the callback.
* @param callback A callback used to receive the dispatch result.
* @param handler {@link Handler} that the dispatch result will be delivered to.
* @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
* {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
* {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
* be invoked later.
* @throws IllegalArgumentException if any of the necessary arguments is {@code null}.
* @hide
*/
public int dispatchInputEvent(InputEvent event, Object token,
FinishedInputEventCallback callback, Handler handler) {
if (event == null) {
throw new IllegalArgumentException("event cannot be null");
}
if (callback != null && handler == null) {
throw new IllegalArgumentException("handler cannot be null");
}
synchronized (mHandler) {
if (mChannel == null) {
return DISPATCH_NOT_HANDLED;
}
PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
if (Looper.myLooper() == Looper.getMainLooper()) {
// Already running on the main thread so we can send the event immediately.
return sendInputEventOnMainLooperLocked(p);
}
// Post the event to the main thread.
Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
msg.setAsynchronous(true);
mHandler.sendMessage(msg);
return DISPATCH_IN_PROGRESS;
}
}
/**
* Callback that is invoked when an input event that was dispatched to this session has been
* finished.
*
* @hide
*/
public interface FinishedInputEventCallback {
/**
* Called when the dispatched input event is finished.
*
* @param token a token passed to {@link #dispatchInputEvent}.
* @param handled {@code true} if the dispatched input event was handled properly.
* {@code false} otherwise.
*/
public void onFinishedInputEvent(Object token, boolean handled);
}
// Must be called on the main looper
private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
synchronized (mHandler) {
int result = sendInputEventOnMainLooperLocked(p);
if (result == DISPATCH_IN_PROGRESS) {
return;
}
}
invokeFinishedInputEventCallback(p, false);
}
private int sendInputEventOnMainLooperLocked(PendingEvent p) {
if (mChannel != null) {
if (mSender == null) {
mSender = new TvInputEventSender(mChannel, mHandler.getLooper());
}
final InputEvent event = p.mEvent;
final int seq = event.getSequenceNumber();
if (mSender.sendInputEvent(seq, event)) {
mPendingEvents.put(seq, p);
Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
return DISPATCH_IN_PROGRESS;
}
Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
+ event);
}
return DISPATCH_NOT_HANDLED;
}
void finishedInputEvent(int seq, boolean handled, boolean timeout) {
final PendingEvent p;
synchronized (mHandler) {
int index = mPendingEvents.indexOfKey(seq);
if (index < 0) {
return; // spurious, event already finished or timed out
}
p = mPendingEvents.valueAt(index);
mPendingEvents.removeAt(index);
if (timeout) {
Log.w(TAG, "Timeout waiting for seesion to handle input event after "
+ INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
} else {
mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
}
}
invokeFinishedInputEventCallback(p, handled);
}
// Assumes the event has already been removed from the queue.
void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
p.mHandled = handled;
if (p.mHandler.getLooper().isCurrentThread()) {
// Already running on the callback handler thread so we can send the callback
// immediately.
p.run();
} else {
// Post the event to the callback handler thread.
// In this case, the callback will be responsible for recycling the event.
Message msg = Message.obtain(p.mHandler, p);
msg.setAsynchronous(true);
msg.sendToTarget();
}
}
private void flushPendingEventsLocked() {
mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
final int count = mPendingEvents.size();
for (int i = 0; i < count; i++) {
int seq = mPendingEvents.keyAt(i);
Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
msg.setAsynchronous(true);
msg.sendToTarget();
}
}
private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
FinishedInputEventCallback callback, Handler handler) {
PendingEvent p = mPendingEventPool.acquire();
if (p == null) {
p = new PendingEvent();
}
p.mEvent = event;
p.mToken = token;
p.mCallback = callback;
p.mHandler = handler;
return p;
}
private void recyclePendingEventLocked(PendingEvent p) {
p.recycle();
mPendingEventPool.release(p);
}
private void releaseInternal() {
mToken = null;
synchronized (mHandler) {
if (mChannel != null) {
if (mSender != null) {
flushPendingEventsLocked();
mSender.dispose();
mSender = null;
}
mChannel.dispose();
mChannel = null;
}
}
synchronized (mSessionCallbackRecordMap) {
mSessionCallbackRecordMap.remove(mSeq);
}
}
private final class InputEventHandler extends Handler {
public static final int MSG_SEND_INPUT_EVENT = 1;
public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
public static final int MSG_FLUSH_INPUT_EVENT = 3;
InputEventHandler(Looper looper) {
super(looper, null, true);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SEND_INPUT_EVENT: {
sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
return;
}
case MSG_TIMEOUT_INPUT_EVENT: {
finishedInputEvent(msg.arg1, false, true);
return;
}
case MSG_FLUSH_INPUT_EVENT: {
finishedInputEvent(msg.arg1, false, false);
return;
}
}
}
}
private final class TvInputEventSender extends InputEventSender {
public TvInputEventSender(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}
@Override
public void onInputEventFinished(int seq, boolean handled) {
finishedInputEvent(seq, handled, false);
}
}
private final class PendingEvent implements Runnable {
public InputEvent mEvent;
public Object mToken;
public FinishedInputEventCallback mCallback;
public Handler mHandler;
public boolean mHandled;
public void recycle() {
mEvent = null;
mToken = null;
mCallback = null;
mHandler = null;
mHandled = false;
}
@Override
public void run() {
mCallback.onFinishedInputEvent(mToken, mHandled);
synchronized (mHandler) {
recyclePendingEventLocked(this);
}
}
}
}
}

View File

@@ -1,598 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.tv.TvInputManager.Session;
import android.util.Log;
import android.view.Gravity;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
/**
* A base class for implementing television input service.
*/
public abstract class TvInputService extends Service {
// STOPSHIP: Turn debugging off.
private static final boolean DEBUG = true;
private static final String TAG = "TvInputService";
/**
* This is the interface name that a service implementing a TV input should say that it support
* -- that is, this is the action it uses for its intent filter. To be supported, the service
* must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that
* other applications cannot abuse it.
*/
public static final String SERVICE_INTERFACE = "android.tv.TvInputService";
private String mId;
private final Handler mHandler = new ServiceHandler();
private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
new RemoteCallbackList<ITvInputServiceCallback>();
private boolean mAvailable;
@Override
public void onCreate() {
super.onCreate();
mId = TvInputInfo.generateInputIdForComponentName(
new ComponentName(getPackageName(), getClass().getName()));
}
@Override
public final IBinder onBind(Intent intent) {
return new ITvInputService.Stub() {
@Override
public void registerCallback(ITvInputServiceCallback cb) {
if (cb != null) {
mCallbacks.register(cb);
// The first time a callback is registered, the service needs to report its
// availability status so that the system can know its initial value.
try {
cb.onAvailabilityChanged(mId, mAvailable);
} catch (RemoteException e) {
Log.e(TAG, "error in onAvailabilityChanged", e);
}
}
}
@Override
public void unregisterCallback(ITvInputServiceCallback cb) {
if (cb != null) {
mCallbacks.unregister(cb);
}
}
@Override
public void createSession(InputChannel channel, ITvInputSessionCallback cb) {
if (channel == null) {
Log.w(TAG, "Creating session without input channel");
}
if (cb == null) {
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = channel;
args.arg2 = cb;
mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
}
};
}
/**
* Convenience method to notify an availability change of this TV input service.
*
* @param available {@code true} if the input service is available to show TV programs.
*/
public final void setAvailable(boolean available) {
if (available != mAvailable) {
mAvailable = available;
mHandler.obtainMessage(ServiceHandler.DO_BROADCAST_AVAILABILITY_CHANGE, available)
.sendToTarget();
}
}
/**
* Get the number of callbacks that are registered.
*
* @hide
*/
@VisibleForTesting
public final int getRegisteredCallbackCount() {
return mCallbacks.getRegisteredCallbackCount();
}
/**
* Returns a concrete implementation of {@link TvInputSessionImpl}.
* <p>
* May return {@code null} if this TV input service fails to create a session for some reason.
* </p>
*/
public abstract TvInputSessionImpl onCreateSession();
/**
* Base class for derived classes to implement to provide {@link TvInputManager.Session}.
*/
public abstract class TvInputSessionImpl implements KeyEvent.Callback {
private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
private final WindowManager mWindowManager;
private WindowManager.LayoutParams mWindowParams;
private Surface mSurface;
private View mOverlayView;
private boolean mOverlayViewEnabled;
private IBinder mWindowToken;
private Rect mOverlayFrame;
private ITvInputSessionCallback mSessionCallback;
public TvInputSessionImpl() {
mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
}
/**
* Enables or disables the overlay view. By default, the overlay view is disabled. Must be
* called explicitly after the session is created to enable the overlay view.
*
* @param enable {@code true} if you want to enable the overlay view. {@code false}
* otherwise.
*/
public void setOverlayViewEnabled(final boolean enable) {
mHandler.post(new Runnable() {
@Override
public void run() {
if (enable == mOverlayViewEnabled) {
return;
}
mOverlayViewEnabled = enable;
if (enable) {
if (mWindowToken != null) {
createOverlayView(mWindowToken, mOverlayFrame);
}
} else {
removeOverlayView(false);
}
}
});
}
/**
* Dispatches an event to the application using this session.
*
* @param eventType The type of the event.
* @param eventArgs Optional arguments of the event.
* @hide
*/
public void dispatchSessionEvent(final String eventType, final Bundle eventArgs) {
if (eventType == null) {
throw new IllegalArgumentException("eventType should not be null.");
}
mHandler.post(new Runnable() {
@Override
public void run() {
try {
if (DEBUG) Log.d(TAG, "dispatchSessionEvent(" + eventType + ")");
mSessionCallback.onSessionEvent(eventType, eventArgs);
} catch (RemoteException e) {
Log.w(TAG, "error in sending event (event=" + eventType + ")");
}
}
});
}
/**
* Sends the change on the size of the video. This is expected to be called at the
* beginning of the playback and later when the size has been changed.
*
* @param width The width of the video.
* @param height The height of the video.
* @hide
*/
public void dispatchVideoSizeChanged(final int width, final int height) {
mHandler.post(new Runnable() {
@Override
public void run() {
try {
if (DEBUG) Log.d(TAG, "dispatchVideoSizeChanged");
mSessionCallback.onVideoSizeChanged(width, height);
} catch (RemoteException e) {
Log.w(TAG, "error in dispatchVideoSizeChanged");
}
}
});
}
/**
* Called when the session is released.
*/
public abstract void onRelease();
/**
* Sets the {@link Surface} for the current input session on which the TV input renders
* video.
*
* @param surface {@link Surface} an application passes to this TV input session.
* @return {@code true} if the surface was set, {@code false} otherwise.
*/
public abstract boolean onSetSurface(Surface surface);
/**
* Sets the relative volume of the current TV input session to handle the change of audio
* focus by setting.
*
* @param volume Volume scale from 0.0 to 1.0.
*/
public abstract void onSetVolume(float volume);
/**
* Tunes to a given channel.
*
* @param channelUri The URI of the channel.
* @return {@code true} the tuning was successful, {@code false} otherwise.
*/
public abstract boolean onTune(Uri channelUri);
/**
* Called when an application requests to create an overlay view. Each session
* implementation can override this method and return its own view.
*
* @return a view attached to the overlay window
*/
public View onCreateOverlayView() {
return null;
}
/**
* Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent)
* KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event).
* <p>
* Override this to intercept key down events before they are processed by the application.
* If you return true, the application will not process the event itself. If you return
* false, the normal application processing will occur as if the TV input had not seen the
* event at all.
*
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
* @return If you handled the event, return {@code true}. If you want to allow the event to
* be handled by the next receiver, return {@code false}.
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
return false;
}
/**
* Default implementation of
* {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
* KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event).
* <p>
* Override this to intercept key long press events before they are processed by the
* application. If you return true, the application will not process the event itself. If
* you return false, the normal application processing will occur as if the TV input had not
* seen the event at all.
*
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
* @return If you handled the event, return {@code true}. If you want to allow the event to
* be handled by the next receiver, return {@code false}.
*/
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return false;
}
/**
* Default implementation of
* {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
* KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event).
* <p>
* Override this to intercept special key multiple events before they are processed by the
* application. If you return true, the application will not itself process the event. If
* you return false, the normal application processing will occur as if the TV input had not
* seen the event at all.
*
* @param keyCode The value in event.getKeyCode().
* @param count The number of times the action was made.
* @param event Description of the key event.
* @return If you handled the event, return {@code true}. If you want to allow the event to
* be handled by the next receiver, return {@code false}.
*/
@Override
public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
return false;
}
/**
* Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent)
* KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event).
* <p>
* Override this to intercept key up events before they are processed by the application. If
* you return true, the application will not itself process the event. If you return false,
* the normal application processing will occur as if the TV input had not seen the event at
* all.
*
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
* @return If you handled the event, return {@code true}. If you want to allow the event to
* be handled by the next receiver, return {@code false}.
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return false;
}
/**
* Implement this method to handle touch screen motion events on the current input session.
*
* @param event The motion event being received.
* @return If you handled the event, return {@code true}. If you want to allow the event to
* be handled by the next receiver, return {@code false}.
* @see View#onTouchEvent
*/
public boolean onTouchEvent(MotionEvent event) {
return false;
}
/**
* Implement this method to handle trackball events on the current input session.
*
* @param event The motion event being received.
* @return If you handled the event, return {@code true}. If you want to allow the event to
* be handled by the next receiver, return {@code false}.
* @see View#onTrackballEvent
*/
public boolean onTrackballEvent(MotionEvent event) {
return false;
}
/**
* Implement this method to handle generic motion events on the current input session.
*
* @param event The motion event being received.
* @return If you handled the event, return {@code true}. If you want to allow the event to
* be handled by the next receiver, return {@code false}.
* @see View#onGenericMotionEvent
*/
public boolean onGenericMotionEvent(MotionEvent event) {
return false;
}
/**
* This method is called when the application would like to stop using the current input
* session.
*/
void release() {
onRelease();
if (mSurface != null) {
mSurface.release();
mSurface = null;
}
removeOverlayView(true);
}
/**
* Calls {@link #onSetSurface}.
*/
void setSurface(Surface surface) {
onSetSurface(surface);
if (mSurface != null) {
mSurface.release();
}
mSurface = surface;
// TODO: Handle failure.
}
/**
* Calls {@link #onSetVolume}.
*/
void setVolume(float volume) {
onSetVolume(volume);
}
/**
* Calls {@link #onTune}.
*/
void tune(Uri channelUri) {
onTune(channelUri);
// TODO: Handle failure.
}
/**
* Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
* to the overlay window.
*
* @param windowToken A window token of an application.
* @param frame A position of the overlay view.
*/
void createOverlayView(IBinder windowToken, Rect frame) {
if (mOverlayView != null) {
mWindowManager.removeView(mOverlayView);
mOverlayView = null;
}
if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
mWindowToken = windowToken;
mOverlayFrame = frame;
if (!mOverlayViewEnabled) {
return;
}
mOverlayView = onCreateOverlayView();
if (mOverlayView == null) {
return;
}
// TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
// an overlay window above the media window but below the application window.
int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
// We make the overlay view non-focusable and non-touchable so that
// the application that owns the window token can decide whether to consume or
// dispatch the input events.
int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
mWindowParams = new WindowManager.LayoutParams(
frame.right - frame.left, frame.bottom - frame.top,
frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT);
mWindowParams.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
mWindowParams.gravity = Gravity.START | Gravity.TOP;
mWindowParams.token = windowToken;
mWindowManager.addView(mOverlayView, mWindowParams);
}
/**
* Relayouts the current overlay view.
*
* @param frame A new position of the overlay view.
*/
void relayoutOverlayView(Rect frame) {
if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")");
mOverlayFrame = frame;
if (!mOverlayViewEnabled || mOverlayView == null) {
return;
}
mWindowParams.x = frame.left;
mWindowParams.y = frame.top;
mWindowParams.width = frame.right - frame.left;
mWindowParams.height = frame.bottom - frame.top;
mWindowManager.updateViewLayout(mOverlayView, mWindowParams);
}
/**
* Removes the current overlay view.
*/
void removeOverlayView(boolean clearWindowToken) {
if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayView + ")");
if (clearWindowToken) {
mWindowToken = null;
mOverlayFrame = null;
}
if (mOverlayView != null) {
mWindowManager.removeView(mOverlayView);
mOverlayView = null;
mWindowParams = null;
}
}
/**
* Takes care of dispatching incoming input events and tells whether the event was handled.
*/
int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
if (event instanceof KeyEvent) {
if (((KeyEvent) event).dispatch(this, mDispatcherState, this)) {
return Session.DISPATCH_HANDLED;
}
} else if (event instanceof MotionEvent) {
MotionEvent motionEvent = (MotionEvent) event;
final int source = motionEvent.getSource();
if (motionEvent.isTouchEvent()) {
if (onTouchEvent(motionEvent)) {
return Session.DISPATCH_HANDLED;
}
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
if (onTrackballEvent(motionEvent)) {
return Session.DISPATCH_HANDLED;
}
} else {
if (onGenericMotionEvent(motionEvent)) {
return Session.DISPATCH_HANDLED;
}
}
}
if (mOverlayView == null || !mOverlayView.isAttachedToWindow()) {
return Session.DISPATCH_NOT_HANDLED;
}
if (!mOverlayView.hasWindowFocus()) {
mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
}
mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
return Session.DISPATCH_IN_PROGRESS;
}
private void setSessionCallback(ITvInputSessionCallback callback) {
mSessionCallback = callback;
}
}
private final class ServiceHandler extends Handler {
private static final int DO_CREATE_SESSION = 1;
private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
@Override
public final void handleMessage(Message msg) {
switch (msg.what) {
case DO_CREATE_SESSION: {
SomeArgs args = (SomeArgs) msg.obj;
InputChannel channel = (InputChannel) args.arg1;
ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
try {
TvInputSessionImpl sessionImpl = onCreateSession();
if (sessionImpl == null) {
// Failed to create a session.
cb.onSessionCreated(null);
} else {
sessionImpl.setSessionCallback(cb);
ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
sessionImpl, channel);
cb.onSessionCreated(stub);
}
} catch (RemoteException e) {
Log.e(TAG, "error in onSessionCreated");
}
args.recycle();
return;
}
case DO_BROADCAST_AVAILABILITY_CHANGE: {
boolean isAvailable = (Boolean) msg.obj;
int n = mCallbacks.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
mCallbacks.getBroadcastItem(i).onAvailabilityChanged(mId, isAvailable);
}
} catch (RemoteException e) {
Log.e(TAG, "Unexpected exception", e);
} finally {
mCallbacks.finishBroadcast();
}
return;
}
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
return;
}
}
}
}
}

View File

@@ -1,20 +0,0 @@
/*
*
* Copyright 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
parcelable TvStreamConfig;

View File

@@ -1,157 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
/**
* @hide
*/
public class TvStreamConfig implements Parcelable {
static final String TAG = TvStreamConfig.class.getSimpleName();
public final static int STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE = 1;
public final static int STREAM_TYPE_BUFFER_PRODUCER = 2;
private int mStreamId;
private int mType;
// TODO: Revisit if max widht/height really make sense.
private int mMaxWidth;
private int mMaxHeight;
/**
* Generations are incremented once framework receives STREAM_CONFIGURATION_CHANGED event from
* HAL module. Framework should throw away outdated configurations and get new configurations
* via tv_input_device::get_stream_configurations().
*/
private int mGeneration;
public static final Parcelable.Creator<TvStreamConfig> CREATOR =
new Parcelable.Creator<TvStreamConfig>() {
@Override
public TvStreamConfig createFromParcel(Parcel source) {
try {
return new Builder().
streamId(source.readInt()).
type(source.readInt()).
maxWidth(source.readInt()).
maxHeight(source.readInt()).
generation(source.readInt()).build();
} catch (Exception e) {
Log.e(TAG, "Exception creating TvStreamConfig from parcel", e);
return null;
}
}
@Override
public TvStreamConfig[] newArray(int size) {
return new TvStreamConfig[size];
}
};
private TvStreamConfig() {}
public int getStreamId() {
return mStreamId;
}
public int getType() {
return mType;
}
public int getMaxWidth() {
return mMaxWidth;
}
public int getMaxHeight() {
return mMaxHeight;
}
public int getGeneration() {
return mGeneration;
}
// Parcelable
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mStreamId);
dest.writeInt(mType);
dest.writeInt(mMaxWidth);
dest.writeInt(mMaxHeight);
dest.writeInt(mGeneration);
}
/**
* A helper class for creating a TvStreamConfig object.
*/
public static final class Builder {
private Integer mStreamId;
private Integer mType;
private Integer mMaxWidth;
private Integer mMaxHeight;
private Integer mGeneration;
public Builder() {
}
public Builder streamId(int streamId) {
mStreamId = streamId;
return this;
}
public Builder type(int type) {
mType = type;
return this;
}
public Builder maxWidth(int maxWidth) {
mMaxWidth = maxWidth;
return this;
}
public Builder maxHeight(int maxHeight) {
mMaxHeight = maxHeight;
return this;
}
public Builder generation(int generation) {
mGeneration = generation;
return this;
}
public TvStreamConfig build() {
if (mStreamId == null || mType == null || mMaxWidth == null || mMaxHeight == null
|| mGeneration == null) {
throw new UnsupportedOperationException();
}
TvStreamConfig config = new TvStreamConfig();
config.mStreamId = mStreamId;
config.mType = mType;
config.mMaxWidth = mMaxWidth;
config.mMaxHeight = mMaxHeight;
config.mGeneration = mGeneration;
return config;
}
}
}

View File

@@ -1,402 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tv;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.tv.TvInputManager.Session;
import android.tv.TvInputManager.Session.FinishedInputEventCallback;
import android.tv.TvInputManager.SessionCallback;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InputEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewRootImpl;
/**
* View playing TV
*/
public class TvView extends SurfaceView {
// STOPSHIP: Turn debugging off.
private static final boolean DEBUG = true;
private static final String TAG = "TvView";
private final Handler mHandler = new Handler();
private TvInputManager.Session mSession;
private Surface mSurface;
private boolean mOverlayViewCreated;
private Rect mOverlayViewFrame;
private final TvInputManager mTvInputManager;
private SessionCallback mSessionCallback;
private OnUnhandledInputEventListener mOnUnhandledInputEventListener;
private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" + width
+ ", height=" + height + ")");
if (holder.getSurface() == mSurface) {
return;
}
mSurface = holder.getSurface();
setSessionSurface(mSurface);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mSurface = holder.getSurface();
setSessionSurface(mSurface);
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mSurface = null;
setSessionSurface(null);
}
};
private final FinishedInputEventCallback mFinishedInputEventCallback =
new FinishedInputEventCallback() {
@Override
public void onFinishedInputEvent(Object token, boolean handled) {
if (DEBUG) {
Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + handled + ")");
}
if (handled) {
return;
}
// TODO: Re-order unhandled events.
InputEvent event = (InputEvent) token;
if (dispatchUnhandledInputEvent(event)) {
return;
}
ViewRootImpl viewRootImpl = getViewRootImpl();
if (viewRootImpl != null) {
viewRootImpl.dispatchUnhandledInputEvent(event);
}
}
};
public TvView(Context context) {
this(context, null, 0);
}
public TvView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TvView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
getHolder().addCallback(mSurfaceHolderCallback);
mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE);
}
/**
* Binds a TV input to this view. {@link SessionCallback#onSessionCreated} will be
* called to send the result of this binding with {@link TvInputManager.Session}.
* If a TV input is already bound, the input will be unbound from this view and its session
* will be released.
*
* @param inputId the id of TV input which will be bound to this view.
* @param callback called when TV input is bound. The callback sends
* {@link TvInputManager.Session}
* @throws IllegalArgumentException if any of the arguments is {@code null}.
*/
public void bindTvInput(String inputId, SessionCallback callback) {
if (TextUtils.isEmpty(inputId)) {
throw new IllegalArgumentException("inputId cannot be null or an empty string");
}
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
if (mSession != null) {
release();
}
// When bindTvInput is called multiple times before the callback is called,
// only the callback of the last bindTvInput call will be actually called back.
// The previous callbacks will be ignored. For the logic, mSessionCallback
// is newly assigned for every bindTvInput call and compared with
// MySessionCreateCallback.this.
mSessionCallback = new MySessionCallback(callback);
mTvInputManager.createSession(inputId, mSessionCallback, mHandler);
}
/**
* Unbinds a TV input currently bound. Its corresponding {@link TvInputManager.Session}
* is released.
*/
public void unbindTvInput() {
if (mSession != null) {
release();
}
mSessionCallback = null;
}
/**
* Dispatches an unhandled input event to the next receiver.
* <p>
* Except system keys, TvView always consumes input events in the normal flow. This is called
* asynchronously from where the event is dispatched. It gives the host application a chance to
* dispatch the unhandled input events.
*
* @param event The input event.
* @return {@code true} if the event was handled by the view, {@code false} otherwise.
*/
public boolean dispatchUnhandledInputEvent(InputEvent event) {
if (mOnUnhandledInputEventListener != null) {
if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) {
return true;
}
}
return onUnhandledInputEvent(event);
}
/**
* Called when an unhandled input event was also not handled by the user provided callback. This
* is the last chance to handle the unhandled input event in the TvView.
*
* @param event The input event.
* @return If you handled the event, return {@code true}. If you want to allow the event to be
* handled by the next receiver, return {@code false}.
*/
public boolean onUnhandledInputEvent(InputEvent event) {
return false;
}
/**
* Registers a callback to be invoked when an input event was not handled by the bound TV input.
*
* @param listener The callback to invoke when the unhandled input event was received.
*/
public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) {
mOnUnhandledInputEventListener = listener;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (super.dispatchKeyEvent(event)) {
return true;
}
if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")");
if (mSession == null) {
return false;
}
InputEvent copiedEvent = event.copy();
int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
mHandler);
return ret != Session.DISPATCH_NOT_HANDLED;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (super.dispatchTouchEvent(event)) {
return true;
}
if (DEBUG) Log.d(TAG, "dispatchTouchEvent(" + event + ")");
if (mSession == null) {
return false;
}
InputEvent copiedEvent = event.copy();
int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
mHandler);
return ret != Session.DISPATCH_NOT_HANDLED;
}
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
if (super.dispatchTrackballEvent(event)) {
return true;
}
if (DEBUG) Log.d(TAG, "dispatchTrackballEvent(" + event + ")");
if (mSession == null) {
return false;
}
InputEvent copiedEvent = event.copy();
int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
mHandler);
return ret != Session.DISPATCH_NOT_HANDLED;
}
@Override
public boolean dispatchGenericMotionEvent(MotionEvent event) {
if (super.dispatchGenericMotionEvent(event)) {
return true;
}
if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent(" + event + ")");
if (mSession == null) {
return false;
}
InputEvent copiedEvent = event.copy();
int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback,
mHandler);
return ret != Session.DISPATCH_NOT_HANDLED;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
createSessionOverlayView();
}
@Override
protected void onDetachedFromWindow() {
removeSessionOverlayView();
super.onDetachedFromWindow();
}
/** @hide */
@Override
protected void updateWindow(boolean force, boolean redrawNeeded) {
super.updateWindow(force, redrawNeeded);
relayoutSessionOverlayView();
}
private void release() {
setSessionSurface(null);
removeSessionOverlayView();
mSession.release();
mSession = null;
}
private void setSessionSurface(Surface surface) {
if (mSession == null) {
return;
}
mSession.setSurface(surface);
}
private void createSessionOverlayView() {
if (mSession == null || !isAttachedToWindow()
|| mOverlayViewCreated) {
return;
}
mOverlayViewFrame = getViewFrameOnScreen();
mSession.createOverlayView(this, mOverlayViewFrame);
mOverlayViewCreated = true;
}
private void removeSessionOverlayView() {
if (mSession == null || !mOverlayViewCreated) {
return;
}
mSession.removeOverlayView();
mOverlayViewCreated = false;
mOverlayViewFrame = null;
}
private void relayoutSessionOverlayView() {
if (mSession == null || !isAttachedToWindow()
|| !mOverlayViewCreated) {
return;
}
Rect viewFrame = getViewFrameOnScreen();
if (viewFrame.equals(mOverlayViewFrame)) {
return;
}
mSession.relayoutOverlayView(viewFrame);
mOverlayViewFrame = viewFrame;
}
private Rect getViewFrameOnScreen() {
int[] location = new int[2];
getLocationOnScreen(location);
return new Rect(location[0], location[1],
location[0] + getWidth(), location[1] + getHeight());
}
/**
* Interface definition for a callback to be invoked when the unhandled input event is received.
*/
public interface OnUnhandledInputEventListener {
/**
* Called when an input event was not handled by the bound TV input.
* <p>
* This is called asynchronously from where the event is dispatched. It gives the host
* application a chance to handle the unhandled input events.
*
* @param event The input event.
* @return If you handled the event, return {@code true}. If you want to allow the event to
* be handled by the next receiver, return {@code false}.
*/
boolean onUnhandledInputEvent(InputEvent event);
}
private class MySessionCallback extends SessionCallback {
final SessionCallback mExternalCallback;
MySessionCallback(SessionCallback externalCallback) {
mExternalCallback = externalCallback;
}
@Override
public void onSessionCreated(Session session) {
if (this != mSessionCallback) {
// This callback is obsolete.
if (session != null) {
session.release();
}
return;
}
mSession = session;
if (session != null) {
// mSurface may not be ready yet as soon as starting an application.
// In the case, we don't send Session.setSurface(null) unnecessarily.
// setSessionSurface will be called in surfaceCreated.
if (mSurface != null) {
setSessionSurface(mSurface);
}
createSessionOverlayView();
}
if (mExternalCallback != null) {
mExternalCallback.onSessionCreated(session);
}
}
@Override
public void onSessionReleased(Session session) {
mSession = null;
if (mExternalCallback != null) {
mExternalCallback.onSessionReleased(session);
}
}
@Override
public void onVideoSizeChanged(Session session, int width, int height) {
if (DEBUG) {
Log.d(TAG, "onVideoSizeChanged(" + width + ", " + height + ")");
}
if (mExternalCallback != null) {
mExternalCallback.onVideoSizeChanged(session, width, height);
}
}
@Override
public void onSessionEvent(TvInputManager.Session session, String eventType,
Bundle eventArgs) {
if (mExternalCallback != null) {
mExternalCallback.onSessionEvent(session, eventType, eventArgs);
}
}
}
}