diff options
74 files changed, 3590 insertions, 321 deletions
diff --git a/api/current.txt b/api/current.txt index b7f63bcb18c7..230d22eb6dd7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -13375,6 +13375,7 @@ package android.hardware { field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD = "android.sensor.magnetic_field"; field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD_UNCALIBRATED = "android.sensor.magnetic_field_uncalibrated"; field public static final deprecated java.lang.String STRING_TYPE_ORIENTATION = "android.sensor.orientation"; + field public static final java.lang.String STRING_TYPE_POSE_6DOF = "android.sensor.pose_6dof"; field public static final java.lang.String STRING_TYPE_PRESSURE = "android.sensor.pressure"; field public static final java.lang.String STRING_TYPE_PROXIMITY = "android.sensor.proximity"; field public static final java.lang.String STRING_TYPE_RELATIVE_HUMIDITY = "android.sensor.relative_humidity"; @@ -13397,6 +13398,7 @@ package android.hardware { field public static final int TYPE_MAGNETIC_FIELD = 2; // 0x2 field public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14; // 0xe field public static final deprecated int TYPE_ORIENTATION = 3; // 0x3 + field public static final int TYPE_POSE_6DOF = 28; // 0x1c field public static final int TYPE_PRESSURE = 6; // 0x6 field public static final int TYPE_PROXIMITY = 8; // 0x8 field public static final int TYPE_RELATIVE_HUMIDITY = 12; // 0xc @@ -22214,6 +22216,7 @@ package android.media.tv { method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri); method public static final android.net.Uri buildProgramsUriForChannel(long, long, long); method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri, long, long); + method public static final android.net.Uri buildRecordedProgramUri(long); field public static final java.lang.String AUTHORITY = "android.media.tv"; } @@ -22350,13 +22353,49 @@ package android.media.tv { field public static final java.lang.String TRAVEL = "TRAVEL"; } + public static final class TvContract.RecordedPrograms implements android.media.tv.TvContract.BaseTvColumns { + field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language"; + field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre"; + field public static final java.lang.String COLUMN_CANONICAL_GENRE = "canonical_genre"; + field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id"; + field public static final java.lang.String COLUMN_CONTENT_RATING = "content_rating"; + field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; + field public static final java.lang.String COLUMN_EPISODE_NUMBER = "episode_number"; + field public static final java.lang.String COLUMN_EPISODE_TITLE = "episode_title"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4"; + field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description"; + field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri"; + field public static final java.lang.String COLUMN_RECORDING_DATA_BYTES = "recording_data_bytes"; + field public static final java.lang.String COLUMN_RECORDING_DATA_URI = "recording_data_uri"; + field public static final java.lang.String COLUMN_RECORDING_DURATION_MILLIS = "recording_duration_millis"; + field public static final java.lang.String COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS = "recording_expire_time_utc_millis"; + field public static final java.lang.String COLUMN_SEARCHABLE = "searchable"; + field public static final java.lang.String COLUMN_SEASON_NUMBER = "season_number"; + field public static final java.lang.String COLUMN_SHORT_DESCRIPTION = "short_description"; + field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; + field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; + field public static final java.lang.String COLUMN_TITLE = "title"; + field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number"; + field public static final java.lang.String COLUMN_VIDEO_HEIGHT = "video_height"; + field public static final java.lang.String COLUMN_VIDEO_WIDTH = "video_width"; + field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/recorded_program"; + field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/recorded_program"; + field public static final android.net.Uri CONTENT_URI; + } + public final class TvInputInfo implements android.os.Parcelable { + method public boolean canRecord(); method public android.content.Intent createSettingsIntent(); method public android.content.Intent createSetupIntent(); method public int describeContents(); method public java.lang.String getId(); method public java.lang.String getParentId(); method public android.content.pm.ServiceInfo getServiceInfo(); + method public int getTunerCount(); method public int getType(); method public boolean isPassthroughInput(); method public android.graphics.drawable.Drawable loadIcon(android.content.Context); @@ -22391,6 +22430,10 @@ package android.media.tv { field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 field public static final java.lang.String META_DATA_CONTENT_RATING_SYSTEMS = "android.media.tv.metadata.CONTENT_RATING_SYSTEMS"; + field public static final int RECORDING_ERROR_CONNECTION_FAILED = 1; // 0x1 + field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 2; // 0x2 + field public static final int RECORDING_ERROR_RESOURCE_BUSY = 3; // 0x3 + field public static final int RECORDING_ERROR_UNKNOWN = 0; // 0x0 field public static final long TIME_SHIFT_INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L field public static final int TIME_SHIFT_STATUS_AVAILABLE = 3; // 0x3 field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2 @@ -22408,12 +22451,15 @@ package android.media.tv { method public void onInputAdded(java.lang.String); method public void onInputRemoved(java.lang.String); method public void onInputStateChanged(java.lang.String, int); + method public void onTvInputInfoChanged(java.lang.String, android.media.tv.TvInputInfo); } public abstract class TvInputService extends android.app.Service { ctor public TvInputService(); method public final android.os.IBinder onBind(android.content.Intent); + method public abstract android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(java.lang.String); method public abstract android.media.tv.TvInputService.Session onCreateSession(java.lang.String); + method public final void setTvInputInfo(java.lang.String, android.media.tv.TvInputInfo); field public static final java.lang.String SERVICE_INTERFACE = "android.media.tv.TvInputService"; field public static final java.lang.String SERVICE_META_DATA = "android.media.tv.input"; } @@ -22426,6 +22472,18 @@ package android.media.tv { method public final boolean onSetSurface(android.view.Surface); } + public static abstract class TvInputService.RecordingSession { + ctor public TvInputService.RecordingSession(android.content.Context); + method public void notifyConnected(); + method public void notifyError(int); + method public void notifyRecordingStarted(); + method public void notifyRecordingStopped(android.net.Uri); + method public abstract void onConnect(android.net.Uri); + method public abstract void onDisconnect(); + method public abstract void onStartRecording(); + method public abstract void onStopRecording(); + } + public static abstract class TvInputService.Session implements android.view.KeyEvent.Callback { ctor public TvInputService.Session(android.content.Context); method public void layoutSurface(int, int, int, int); @@ -22453,6 +22511,7 @@ package android.media.tv { method public long onTimeShiftGetCurrentPosition(); method public long onTimeShiftGetStartPosition(); method public void onTimeShiftPause(); + method public void onTimeShiftPlay(android.net.Uri); method public void onTimeShiftResume(); method public void onTimeShiftSeekTo(long); method public void onTimeShiftSetPlaybackParams(android.media.PlaybackParams); @@ -22463,6 +22522,23 @@ package android.media.tv { method public void setOverlayViewEnabled(boolean); } + public class TvRecordingClient { + ctor public TvRecordingClient(android.content.Context, java.lang.String, android.media.tv.TvRecordingClient.RecordingCallback, android.os.Handler); + method public void connect(java.lang.String, android.net.Uri); + method public void disconnect(); + method public void startRecording(); + method public void stopRecording(); + } + + public class TvRecordingClient.RecordingCallback { + ctor public TvRecordingClient.RecordingCallback(); + method public void onConnected(); + method public void onDisconnected(); + method public void onError(int); + method public void onRecordingStarted(); + method public void onRecordingStopped(android.net.Uri); + } + public final class TvTrackInfo implements android.os.Parcelable { method public int describeContents(); method public final int getAudioChannelCount(); @@ -22514,6 +22590,7 @@ package android.media.tv { method public void setStreamVolume(float); method public void setTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback); method public void timeShiftPause(); + method public void timeShiftPlay(java.lang.String, android.net.Uri); method public void timeShiftResume(); method public void timeShiftSeekTo(long); method public void timeShiftSetPlaybackParams(android.media.PlaybackParams); @@ -32855,6 +32932,7 @@ package android.renderscript { method public void getVarV(int, android.renderscript.FieldPacker); method protected void invoke(int); method protected void invoke(int, android.renderscript.FieldPacker); + method protected void reduce(int, android.renderscript.Allocation[], android.renderscript.Allocation, android.renderscript.Script.LaunchOptions); method public void setTimeZone(java.lang.String); method public void setVar(int, float); method public void setVar(int, double); @@ -35873,6 +35951,8 @@ package android.telephony { public final class CellIdentityGsm implements android.os.Parcelable { method public int describeContents(); + method public int getArfcn(); + method public int getBsic(); method public int getCid(); method public int getLac(); method public int getMcc(); @@ -35885,6 +35965,7 @@ package android.telephony { public final class CellIdentityLte implements android.os.Parcelable { method public int describeContents(); method public int getCi(); + method public int getEarfcn(); method public int getMcc(); method public int getMnc(); method public int getPci(); @@ -35900,6 +35981,7 @@ package android.telephony { method public int getMcc(); method public int getMnc(); method public int getPsc(); + method public int getUarfcn(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityWcdma> CREATOR; } diff --git a/api/system-current.txt b/api/system-current.txt index 55f08c888792..e4f42c05843f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -13772,6 +13772,7 @@ package android.hardware { field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD = "android.sensor.magnetic_field"; field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD_UNCALIBRATED = "android.sensor.magnetic_field_uncalibrated"; field public static final deprecated java.lang.String STRING_TYPE_ORIENTATION = "android.sensor.orientation"; + field public static final java.lang.String STRING_TYPE_POSE_6DOF = "android.sensor.pose_6dof"; field public static final java.lang.String STRING_TYPE_PRESSURE = "android.sensor.pressure"; field public static final java.lang.String STRING_TYPE_PROXIMITY = "android.sensor.proximity"; field public static final java.lang.String STRING_TYPE_RELATIVE_HUMIDITY = "android.sensor.relative_humidity"; @@ -13795,6 +13796,7 @@ package android.hardware { field public static final int TYPE_MAGNETIC_FIELD = 2; // 0x2 field public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14; // 0xe field public static final deprecated int TYPE_ORIENTATION = 3; // 0x3 + field public static final int TYPE_POSE_6DOF = 28; // 0x1c field public static final int TYPE_PRESSURE = 6; // 0x6 field public static final int TYPE_PROXIMITY = 8; // 0x8 field public static final int TYPE_RELATIVE_HUMIDITY = 12; // 0xc @@ -23641,6 +23643,7 @@ package android.media.tv { method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri); method public static final android.net.Uri buildProgramsUriForChannel(long, long, long); method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri, long, long); + method public static final android.net.Uri buildRecordedProgramUri(long); method public static final boolean isChannelUriForPassthroughInput(android.net.Uri); field public static final java.lang.String AUTHORITY = "android.media.tv"; } @@ -23781,6 +23784,40 @@ package android.media.tv { field public static final java.lang.String TRAVEL = "TRAVEL"; } + public static final class TvContract.RecordedPrograms implements android.media.tv.TvContract.BaseTvColumns { + field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language"; + field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre"; + field public static final java.lang.String COLUMN_CANONICAL_GENRE = "canonical_genre"; + field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id"; + field public static final java.lang.String COLUMN_CONTENT_RATING = "content_rating"; + field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; + field public static final java.lang.String COLUMN_EPISODE_NUMBER = "episode_number"; + field public static final java.lang.String COLUMN_EPISODE_TITLE = "episode_title"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4"; + field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description"; + field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri"; + field public static final java.lang.String COLUMN_RECORDING_DATA_BYTES = "recording_data_bytes"; + field public static final java.lang.String COLUMN_RECORDING_DATA_URI = "recording_data_uri"; + field public static final java.lang.String COLUMN_RECORDING_DURATION_MILLIS = "recording_duration_millis"; + field public static final java.lang.String COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS = "recording_expire_time_utc_millis"; + field public static final java.lang.String COLUMN_SEARCHABLE = "searchable"; + field public static final java.lang.String COLUMN_SEASON_NUMBER = "season_number"; + field public static final java.lang.String COLUMN_SHORT_DESCRIPTION = "short_description"; + field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; + field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; + field public static final java.lang.String COLUMN_TITLE = "title"; + field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number"; + field public static final java.lang.String COLUMN_VIDEO_HEIGHT = "video_height"; + field public static final java.lang.String COLUMN_VIDEO_WIDTH = "video_width"; + field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/recorded_program"; + field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/recorded_program"; + field public static final android.net.Uri CONTENT_URI; + } + public static final class TvContract.WatchedPrograms implements android.media.tv.TvContract.BaseTvColumns { field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id"; field public static final java.lang.String COLUMN_DESCRIPTION = "description"; @@ -23829,6 +23866,7 @@ package android.media.tv { } public final class TvInputInfo implements android.os.Parcelable { + method public boolean canRecord(); method public android.content.Intent createSettingsIntent(); method public android.content.Intent createSetupIntent(); method public static android.media.tv.TvInputInfo createTvInputInfo(android.content.Context, android.content.pm.ResolveInfo, android.hardware.hdmi.HdmiDeviceInfo, java.lang.String, java.lang.String, android.net.Uri) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; @@ -23840,6 +23878,7 @@ package android.media.tv { method public java.lang.String getId(); method public java.lang.String getParentId(); method public android.content.pm.ServiceInfo getServiceInfo(); + method public int getTunerCount(); method public int getType(); method public boolean isConnectedToHdmiSwitch(); method public boolean isHardwareInput(); @@ -23874,6 +23913,7 @@ package android.media.tv { method public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputManager.HardwareCallback, android.media.tv.TvInputInfo); method public void addBlockedRating(android.media.tv.TvContentRating); method public boolean captureFrame(java.lang.String, android.view.Surface, android.media.tv.TvStreamConfig); + method public void createRecordingSession(java.lang.String, android.media.tv.TvInputManager.SessionCallback, android.os.Handler); method public void createSession(java.lang.String, android.media.tv.TvInputManager.SessionCallback, android.os.Handler); method public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(java.lang.String); method public java.util.List<android.media.tv.TvContentRating> getBlockedRatings(); @@ -23897,6 +23937,10 @@ package android.media.tv { field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 field public static final java.lang.String META_DATA_CONTENT_RATING_SYSTEMS = "android.media.tv.metadata.CONTENT_RATING_SYSTEMS"; + field public static final int RECORDING_ERROR_CONNECTION_FAILED = 1; // 0x1 + field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 2; // 0x2 + field public static final int RECORDING_ERROR_RESOURCE_BUSY = 3; // 0x3 + field public static final int RECORDING_ERROR_UNKNOWN = 0; // 0x0 field public static final long TIME_SHIFT_INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L field public static final int TIME_SHIFT_STATUS_AVAILABLE = 3; // 0x3 field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2 @@ -23961,16 +24005,19 @@ package android.media.tv { method public void onInputRemoved(java.lang.String); method public void onInputStateChanged(java.lang.String, int); method public void onInputUpdated(java.lang.String); + method public void onTvInputInfoChanged(java.lang.String, android.media.tv.TvInputInfo); } public abstract class TvInputService extends android.app.Service { ctor public TvInputService(); method public final android.os.IBinder onBind(android.content.Intent); + method public abstract android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(java.lang.String); method public abstract android.media.tv.TvInputService.Session onCreateSession(java.lang.String); method public android.media.tv.TvInputInfo onHardwareAdded(android.media.tv.TvInputHardwareInfo); method public java.lang.String onHardwareRemoved(android.media.tv.TvInputHardwareInfo); method public android.media.tv.TvInputInfo onHdmiDeviceAdded(android.hardware.hdmi.HdmiDeviceInfo); method public java.lang.String onHdmiDeviceRemoved(android.hardware.hdmi.HdmiDeviceInfo); + method public final void setTvInputInfo(java.lang.String, android.media.tv.TvInputInfo); field public static final java.lang.String SERVICE_INTERFACE = "android.media.tv.TvInputService"; field public static final java.lang.String SERVICE_META_DATA = "android.media.tv.input"; } @@ -23983,6 +24030,21 @@ package android.media.tv { method public final boolean onSetSurface(android.view.Surface); } + public static abstract class TvInputService.RecordingSession { + ctor public TvInputService.RecordingSession(android.content.Context); + method public void notifyConnected(); + method public void notifyError(int); + method public void notifyRecordingStarted(); + method public void notifyRecordingStopped(android.net.Uri); + method public void notifySessionEvent(java.lang.String, android.os.Bundle); + method public void onAppPrivateCommand(java.lang.String, android.os.Bundle); + method public abstract void onConnect(android.net.Uri); + method public void onConnect(android.net.Uri, android.os.Bundle); + method public abstract void onDisconnect(); + method public abstract void onStartRecording(); + method public abstract void onStopRecording(); + } + public static abstract class TvInputService.Session implements android.view.KeyEvent.Callback { ctor public TvInputService.Session(android.content.Context); method public void layoutSurface(int, int, int, int); @@ -24013,6 +24075,7 @@ package android.media.tv { method public long onTimeShiftGetCurrentPosition(); method public long onTimeShiftGetStartPosition(); method public void onTimeShiftPause(); + method public void onTimeShiftPlay(android.net.Uri); method public void onTimeShiftResume(); method public void onTimeShiftSeekTo(long); method public void onTimeShiftSetPlaybackParams(android.media.PlaybackParams); @@ -24024,6 +24087,26 @@ package android.media.tv { method public void setOverlayViewEnabled(boolean); } + public class TvRecordingClient { + ctor public TvRecordingClient(android.content.Context, java.lang.String, android.media.tv.TvRecordingClient.RecordingCallback, android.os.Handler); + method public void connect(java.lang.String, android.net.Uri); + method public void connect(java.lang.String, android.net.Uri, android.os.Bundle); + method public void disconnect(); + method public void sendAppPrivateCommand(java.lang.String, android.os.Bundle); + method public void startRecording(); + method public void stopRecording(); + } + + public class TvRecordingClient.RecordingCallback { + ctor public TvRecordingClient.RecordingCallback(); + method public void onConnected(); + method public void onDisconnected(); + method public void onError(int); + method public void onEvent(java.lang.String, java.lang.String, android.os.Bundle); + method public void onRecordingStarted(); + method public void onRecordingStopped(android.net.Uri); + } + public class TvStreamConfig implements android.os.Parcelable { method public int describeContents(); method public int getFlags(); @@ -24106,6 +24189,7 @@ package android.media.tv { method public void setZOrderMediaOverlay(boolean); method public void setZOrderOnTop(boolean); method public void timeShiftPause(); + method public void timeShiftPlay(java.lang.String, android.net.Uri); method public void timeShiftResume(); method public void timeShiftSeekTo(long); method public void timeShiftSetPlaybackParams(android.media.PlaybackParams); @@ -35052,6 +35136,7 @@ package android.renderscript { method public void getVarV(int, android.renderscript.FieldPacker); method protected void invoke(int); method protected void invoke(int, android.renderscript.FieldPacker); + method protected void reduce(int, android.renderscript.Allocation[], android.renderscript.Allocation, android.renderscript.Script.LaunchOptions); method public void setTimeZone(java.lang.String); method public void setVar(int, float); method public void setVar(int, double); @@ -38242,6 +38327,8 @@ package android.telephony { public final class CellIdentityGsm implements android.os.Parcelable { method public int describeContents(); + method public int getArfcn(); + method public int getBsic(); method public int getCid(); method public int getLac(); method public int getMcc(); @@ -38254,6 +38341,7 @@ package android.telephony { public final class CellIdentityLte implements android.os.Parcelable { method public int describeContents(); method public int getCi(); + method public int getEarfcn(); method public int getMcc(); method public int getMnc(); method public int getPci(); @@ -38269,6 +38357,7 @@ package android.telephony { method public int getMcc(); method public int getMnc(); method public int getPsc(); + method public int getUarfcn(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityWcdma> CREATOR; } diff --git a/api/test-current.txt b/api/test-current.txt index e320b6742ff6..1a9b469a991e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -13383,6 +13383,7 @@ package android.hardware { field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD = "android.sensor.magnetic_field"; field public static final java.lang.String STRING_TYPE_MAGNETIC_FIELD_UNCALIBRATED = "android.sensor.magnetic_field_uncalibrated"; field public static final deprecated java.lang.String STRING_TYPE_ORIENTATION = "android.sensor.orientation"; + field public static final java.lang.String STRING_TYPE_POSE_6DOF = "android.sensor.pose_6dof"; field public static final java.lang.String STRING_TYPE_PRESSURE = "android.sensor.pressure"; field public static final java.lang.String STRING_TYPE_PROXIMITY = "android.sensor.proximity"; field public static final java.lang.String STRING_TYPE_RELATIVE_HUMIDITY = "android.sensor.relative_humidity"; @@ -13405,6 +13406,7 @@ package android.hardware { field public static final int TYPE_MAGNETIC_FIELD = 2; // 0x2 field public static final int TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14; // 0xe field public static final deprecated int TYPE_ORIENTATION = 3; // 0x3 + field public static final int TYPE_POSE_6DOF = 28; // 0x1c field public static final int TYPE_PRESSURE = 6; // 0x6 field public static final int TYPE_PROXIMITY = 8; // 0x8 field public static final int TYPE_RELATIVE_HUMIDITY = 12; // 0xc @@ -22222,6 +22224,7 @@ package android.media.tv { method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri); method public static final android.net.Uri buildProgramsUriForChannel(long, long, long); method public static final android.net.Uri buildProgramsUriForChannel(android.net.Uri, long, long); + method public static final android.net.Uri buildRecordedProgramUri(long); field public static final java.lang.String AUTHORITY = "android.media.tv"; } @@ -22358,13 +22361,49 @@ package android.media.tv { field public static final java.lang.String TRAVEL = "TRAVEL"; } + public static final class TvContract.RecordedPrograms implements android.media.tv.TvContract.BaseTvColumns { + field public static final java.lang.String COLUMN_AUDIO_LANGUAGE = "audio_language"; + field public static final java.lang.String COLUMN_BROADCAST_GENRE = "broadcast_genre"; + field public static final java.lang.String COLUMN_CANONICAL_GENRE = "canonical_genre"; + field public static final java.lang.String COLUMN_CHANNEL_ID = "channel_id"; + field public static final java.lang.String COLUMN_CONTENT_RATING = "content_rating"; + field public static final java.lang.String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis"; + field public static final java.lang.String COLUMN_EPISODE_NUMBER = "episode_number"; + field public static final java.lang.String COLUMN_EPISODE_TITLE = "episode_title"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3"; + field public static final java.lang.String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4"; + field public static final java.lang.String COLUMN_LONG_DESCRIPTION = "long_description"; + field public static final java.lang.String COLUMN_POSTER_ART_URI = "poster_art_uri"; + field public static final java.lang.String COLUMN_RECORDING_DATA_BYTES = "recording_data_bytes"; + field public static final java.lang.String COLUMN_RECORDING_DATA_URI = "recording_data_uri"; + field public static final java.lang.String COLUMN_RECORDING_DURATION_MILLIS = "recording_duration_millis"; + field public static final java.lang.String COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS = "recording_expire_time_utc_millis"; + field public static final java.lang.String COLUMN_SEARCHABLE = "searchable"; + field public static final java.lang.String COLUMN_SEASON_NUMBER = "season_number"; + field public static final java.lang.String COLUMN_SHORT_DESCRIPTION = "short_description"; + field public static final java.lang.String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis"; + field public static final java.lang.String COLUMN_THUMBNAIL_URI = "thumbnail_uri"; + field public static final java.lang.String COLUMN_TITLE = "title"; + field public static final java.lang.String COLUMN_VERSION_NUMBER = "version_number"; + field public static final java.lang.String COLUMN_VIDEO_HEIGHT = "video_height"; + field public static final java.lang.String COLUMN_VIDEO_WIDTH = "video_width"; + field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/recorded_program"; + field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/recorded_program"; + field public static final android.net.Uri CONTENT_URI; + } + public final class TvInputInfo implements android.os.Parcelable { + method public boolean canRecord(); method public android.content.Intent createSettingsIntent(); method public android.content.Intent createSetupIntent(); method public int describeContents(); method public java.lang.String getId(); method public java.lang.String getParentId(); method public android.content.pm.ServiceInfo getServiceInfo(); + method public int getTunerCount(); method public int getType(); method public boolean isPassthroughInput(); method public android.graphics.drawable.Drawable loadIcon(android.content.Context); @@ -22399,6 +22438,10 @@ package android.media.tv { field public static final int INPUT_STATE_CONNECTED_STANDBY = 1; // 0x1 field public static final int INPUT_STATE_DISCONNECTED = 2; // 0x2 field public static final java.lang.String META_DATA_CONTENT_RATING_SYSTEMS = "android.media.tv.metadata.CONTENT_RATING_SYSTEMS"; + field public static final int RECORDING_ERROR_CONNECTION_FAILED = 1; // 0x1 + field public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 2; // 0x2 + field public static final int RECORDING_ERROR_RESOURCE_BUSY = 3; // 0x3 + field public static final int RECORDING_ERROR_UNKNOWN = 0; // 0x0 field public static final long TIME_SHIFT_INVALID_TIME = -9223372036854775808L; // 0x8000000000000000L field public static final int TIME_SHIFT_STATUS_AVAILABLE = 3; // 0x3 field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2 @@ -22416,12 +22459,15 @@ package android.media.tv { method public void onInputAdded(java.lang.String); method public void onInputRemoved(java.lang.String); method public void onInputStateChanged(java.lang.String, int); + method public void onTvInputInfoChanged(java.lang.String, android.media.tv.TvInputInfo); } public abstract class TvInputService extends android.app.Service { ctor public TvInputService(); method public final android.os.IBinder onBind(android.content.Intent); + method public abstract android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(java.lang.String); method public abstract android.media.tv.TvInputService.Session onCreateSession(java.lang.String); + method public final void setTvInputInfo(java.lang.String, android.media.tv.TvInputInfo); field public static final java.lang.String SERVICE_INTERFACE = "android.media.tv.TvInputService"; field public static final java.lang.String SERVICE_META_DATA = "android.media.tv.input"; } @@ -22434,6 +22480,18 @@ package android.media.tv { method public final boolean onSetSurface(android.view.Surface); } + public static abstract class TvInputService.RecordingSession { + ctor public TvInputService.RecordingSession(android.content.Context); + method public void notifyConnected(); + method public void notifyError(int); + method public void notifyRecordingStarted(); + method public void notifyRecordingStopped(android.net.Uri); + method public abstract void onConnect(android.net.Uri); + method public abstract void onDisconnect(); + method public abstract void onStartRecording(); + method public abstract void onStopRecording(); + } + public static abstract class TvInputService.Session implements android.view.KeyEvent.Callback { ctor public TvInputService.Session(android.content.Context); method public void layoutSurface(int, int, int, int); @@ -22461,6 +22519,7 @@ package android.media.tv { method public long onTimeShiftGetCurrentPosition(); method public long onTimeShiftGetStartPosition(); method public void onTimeShiftPause(); + method public void onTimeShiftPlay(android.net.Uri); method public void onTimeShiftResume(); method public void onTimeShiftSeekTo(long); method public void onTimeShiftSetPlaybackParams(android.media.PlaybackParams); @@ -22471,6 +22530,23 @@ package android.media.tv { method public void setOverlayViewEnabled(boolean); } + public class TvRecordingClient { + ctor public TvRecordingClient(android.content.Context, java.lang.String, android.media.tv.TvRecordingClient.RecordingCallback, android.os.Handler); + method public void connect(java.lang.String, android.net.Uri); + method public void disconnect(); + method public void startRecording(); + method public void stopRecording(); + } + + public class TvRecordingClient.RecordingCallback { + ctor public TvRecordingClient.RecordingCallback(); + method public void onConnected(); + method public void onDisconnected(); + method public void onError(int); + method public void onRecordingStarted(); + method public void onRecordingStopped(android.net.Uri); + } + public final class TvTrackInfo implements android.os.Parcelable { method public int describeContents(); method public final int getAudioChannelCount(); @@ -22522,6 +22598,7 @@ package android.media.tv { method public void setStreamVolume(float); method public void setTimeShiftPositionCallback(android.media.tv.TvView.TimeShiftPositionCallback); method public void timeShiftPause(); + method public void timeShiftPlay(java.lang.String, android.net.Uri); method public void timeShiftResume(); method public void timeShiftSeekTo(long); method public void timeShiftSetPlaybackParams(android.media.PlaybackParams); @@ -32869,6 +32946,7 @@ package android.renderscript { method public void getVarV(int, android.renderscript.FieldPacker); method protected void invoke(int); method protected void invoke(int, android.renderscript.FieldPacker); + method protected void reduce(int, android.renderscript.Allocation[], android.renderscript.Allocation, android.renderscript.Script.LaunchOptions); method public void setTimeZone(java.lang.String); method public void setVar(int, float); method public void setVar(int, double); @@ -35887,6 +35965,8 @@ package android.telephony { public final class CellIdentityGsm implements android.os.Parcelable { method public int describeContents(); + method public int getArfcn(); + method public int getBsic(); method public int getCid(); method public int getLac(); method public int getMcc(); @@ -35899,6 +35979,7 @@ package android.telephony { public final class CellIdentityLte implements android.os.Parcelable { method public int describeContents(); method public int getCi(); + method public int getEarfcn(); method public int getMcc(); method public int getMnc(); method public int getPci(); @@ -35914,6 +35995,7 @@ package android.telephony { method public int getMcc(); method public int getMnc(); method public int getPsc(); + method public int getUarfcn(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.telephony.CellIdentityWcdma> CREATOR; } diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl index 560e22a3be1d..fa1123423e35 100644 --- a/core/java/android/app/ITaskStackListener.aidl +++ b/core/java/android/app/ITaskStackListener.aidl @@ -23,4 +23,11 @@ oneway interface ITaskStackListener { /** Called whenever an Activity is moved to the pinned stack from another stack. */ void onActivityPinned(); + + /** + * Called whenever IActivityManager.startActivity is called on an activity that is already + * running in the pinned stack and the activity is not actually started, but the task is either + * brought to the front or a new Intent is delivered to it. + */ + void onPinnedActivityRestartAttempt(); } diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index 2bbc54f7e4e6..999d8260cad3 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -155,7 +155,7 @@ public class TrustManager { } /** - * @return whether {@userId} has enabled and configured trust agents. Ignores short-term + * @return whether {@param userId} has enabled and configured trust agents. Ignores short-term * unavailability of trust due to {@link LockPatternUtils.StrongAuthTracker}. */ @RequiresPermission(android.Manifest.permission.TRUST_LISTENER) diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 01689087043f..654396bc5dfa 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -176,7 +176,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { /** * Value for {@link #flags}: this is set if this application has been - * install as an update to a built-in system application. + * installed as an update to a built-in system application. */ public static final int FLAG_UPDATED_SYSTEM_APP = 1<<7; diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 6935174f58d6..e5efd568abc7 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -551,6 +551,29 @@ public final class Sensor { */ public static final String STRING_TYPE_DEVICE_ORIENTATION = "android.sensor.device_orientation"; + /** + * A constant describing a pose sensor with 6 degrees of freedom. + * + * Similar to {@link #TYPE_ROTATION_VECTOR}, with additional delta + * translation from an arbitrary reference point. + * + * Can use camera, depth sensor etc to compute output value. + * + * This is expected to be a high power sensor and expected only to be + * used when the screen is on. + * + * Expected to be more accurate than the rotation vector alone. + * + */ + public static final int TYPE_POSE_6DOF = 28; + + /** + * A constant string describing a pose sensor with 6 degrees of freedom. + * + * @see #TYPE_POSE_6DOF + */ + public static final String STRING_TYPE_POSE_6DOF = "android.sensor.pose_6dof"; + /** * A constant describing all sensor types. */ @@ -637,6 +660,7 @@ public final class Sensor { 1, // SENSOR_TYPE_PICK_UP_GESTURE 1, // SENSOR_TYPE_WRIST_TILT_GESTURE 1, // SENSOR_TYPE_DEVICE_ORIENTATION + 16, // SENSOR_TYPE_POSE_6DOF }; /** diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java index 9937b2cb2d26..416c74c37f2f 100644 --- a/core/java/android/hardware/SensorEvent.java +++ b/core/java/android/hardware/SensorEvent.java @@ -497,6 +497,40 @@ public class SensorEvent { * <li> 3: device is rotated 90 degrees clockwise from default orientation (X axis * is vertical and points down) * </ul> + * + * <h4>{@link android.hardware.Sensor#TYPE_POSE_6DOF + * Sensor.TYPE_POSE_6DOF}:</h4> + * + * A TYPE_POSE_6DOF event consists of a rotation expressed as a quaternion and a translation + * expressed in SI units. The event also contains a delta rotation and translation that show + * how the device?s pose has changed since the previous sequence numbered pose. + * The event uses the cannonical Android Sensor axes. + * + * + * <ul> + * <li> values[0]: x*sin(θ/2) </li> + * <li> values[1]: y*sin(θ/2) </li> + * <li> values[2]: z*sin(θ/2) </li> + * <li> values[3]: cos(θ/2) </li> + * + * + * <li> values[4]: Translation along x axis from an arbitrary origin. </li> + * li> values[5]: Translation along y axis from an arbitrary origin. </li> + * <li> values[6]: Translation along z axis from an arbitrary origin. </li> + * + * <li> values[7]: Delta quaternion rotation x*sin(θ/2) </li> + * <li> values[8]: Delta quaternion rotation y*sin(θ/2) </li> + * <li> values[9]: Delta quaternion rotation z*sin(θ/2) </li> + * <li> values[10]: Delta quaternion rotation cos(θ/2) </li> + * + * <li> values[11]: Delta translation along x axis. </li> + * <li> values[12]: Delta translation along y axis. </li> + * <li> values[13]: Delta translation along z axis. </li> + * + * <li> values[14]: Sequence number </li> + * + * </ul> + * */ public final float[] values; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 6c3f308f6846..dd0887f8f8c5 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -100,13 +100,13 @@ import android.view.inputmethod.InputMethodManager; import android.widget.Checkable; import android.widget.FrameLayout; import android.widget.ScrollBarDrawable; - import static android.os.Build.VERSION_CODES.*; import static java.lang.Math.max; import com.android.internal.R; import com.android.internal.util.Predicate; import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.widget.ScrollBarUtils; import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -5128,6 +5128,88 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return mVerticalScrollbarPosition; } + boolean isOnScrollbar(float x, float y) { + if (mScrollCache == null) { + return false; + } + x += getScrollX(); + y += getScrollY(); + if (isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden()) { + final Rect bounds = mScrollCache.mScrollBarBounds; + getVerticalScrollBarBounds(bounds); + if (bounds.contains((int)x, (int)y)) { + return true; + } + } + if (isHorizontalScrollBarEnabled()) { + final Rect bounds = mScrollCache.mScrollBarBounds; + getHorizontalScrollBarBounds(bounds); + if (bounds.contains((int)x, (int)y)) { + return true; + } + } + return false; + } + + boolean isOnScrollbarThumb(float x, float y) { + return isOnVerticalScrollbarThumb(x, y) || isOnHorizontalScrollbarThumb(x, y); + } + + private boolean isOnVerticalScrollbarThumb(float x, float y) { + if (mScrollCache == null) { + return false; + } + if (isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden()) { + x += getScrollX(); + y += getScrollY(); + final Rect bounds = mScrollCache.mScrollBarBounds; + getVerticalScrollBarBounds(bounds); + final int range = computeVerticalScrollRange(); + final int offset = computeVerticalScrollOffset(); + final int extent = computeVerticalScrollExtent(); + final int thumbLength = ScrollBarUtils.getThumbLength(bounds.height(), bounds.width(), + extent, range); + final int thumbOffset = ScrollBarUtils.getThumbOffset(bounds.height(), thumbLength, + extent, range, offset); + final int thumbTop = bounds.top + thumbOffset; + if (x >= bounds.left && x <= bounds.right && y >= thumbTop + && y <= thumbTop + thumbLength) { + return true; + } + } + return false; + } + + private boolean isOnHorizontalScrollbarThumb(float x, float y) { + if (mScrollCache == null) { + return false; + } + if (isHorizontalScrollBarEnabled()) { + x += getScrollX(); + y += getScrollY(); + final Rect bounds = mScrollCache.mScrollBarBounds; + getHorizontalScrollBarBounds(bounds); + final int range = computeHorizontalScrollRange(); + final int offset = computeHorizontalScrollOffset(); + final int extent = computeHorizontalScrollExtent(); + final int thumbLength = ScrollBarUtils.getThumbLength(bounds.width(), bounds.height(), + extent, range); + final int thumbOffset = ScrollBarUtils.getThumbOffset(bounds.width(), thumbLength, + extent, range, offset); + final int thumbLeft = bounds.left + thumbOffset; + if (x >= thumbLeft && x <= thumbLeft + thumbLength && y >= bounds.top + && y <= bounds.bottom) { + return true; + } + } + return false; + } + + boolean isDraggingScrollBar() { + return mScrollCache != null + && mScrollCache.mScrollBarDraggingState != ScrollabilityCache.NOT_DRAGGING; + } + /** * Sets the state of all scroll indicators. * <p> @@ -5795,8 +5877,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p>A View should call this if it maintains some notion of which part * of its content is interesting. For example, a text editing view * should call this when its cursor moves. + * <p>The Rectangle passed into this method should be in the View's content coordinate space. + * It should not be affected by which part of the View is currently visible or its scroll + * position. * - * @param rectangle The rectangle. + * @param rectangle The rectangle in the View's content coordinate space * @return Whether any parent scrolled. */ public boolean requestRectangleOnScreen(Rect rectangle) { @@ -5810,11 +5895,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <p>A View should call this if it maintains some notion of which part * of its content is interesting. For example, a text editing view * should call this when its cursor moves. - * + * <p>The Rectangle passed into this method should be in the View's content coordinate space. + * It should not be affected by which part of the View is currently visible or its scroll + * position. * <p>When <code>immediate</code> is set to true, scrolling will not be * animated. * - * @param rectangle The rectangle. + * @param rectangle The rectangle in the View's content coordinate space * @param immediate True to forbid animated scrolling, false otherwise * @return Whether any parent scrolled. */ @@ -5834,24 +5921,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, rectangle.set((int) position.left, (int) position.top, (int) position.right, (int) position.bottom); - scrolled |= parent.requestChildRectangleOnScreen(child, - rectangle, immediate); - - if (!child.hasIdentityMatrix()) { - child.getMatrix().mapRect(position); - } - - position.offset(child.mLeft, child.mTop); + scrolled |= parent.requestChildRectangleOnScreen(child, rectangle, immediate); if (!(parent instanceof View)) { break; } - View parentView = (View) parent; - - position.offset(-parentView.getScrollX(), -parentView.getScrollY()); + // move it from child's content coordinate space to parent's content coordinate space + position.offset(child.mLeft - child.getScrollX(), child.mTop -child.getScrollY()); - child = parentView; + child = (View) parent; parent = child.getParent(); } @@ -9718,6 +9797,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (onFilterTouchEventForSecurity(event)) { + if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) { + result = true; + } //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null @@ -10582,6 +10664,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + if ((action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_MOVE) + && event.isFromSource(InputDevice.SOURCE_MOUSE) + && isOnScrollbar(event.getX(), event.getY())) { + awakenScrollBars(); + } if (isHoverable()) { switch (action) { case MotionEvent.ACTION_HOVER_ENTER: @@ -10685,6 +10772,110 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Handles scroll bar dragging by mouse input. + * + * @hide + * @param event The motion event. + * + * @return true if the event was handled as a scroll bar dragging, false otherwise. + */ + protected boolean handleScrollBarDragging(MotionEvent event) { + if (mScrollCache == null) { + return false; + } + final float x = event.getX(); + final float y = event.getY(); + final int action = event.getAction(); + if ((mScrollCache.mScrollBarDraggingState == ScrollabilityCache.NOT_DRAGGING + && action != MotionEvent.ACTION_DOWN) + || !event.isFromSource(InputDevice.SOURCE_MOUSE) + || !event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { + mScrollCache.mScrollBarDraggingState = ScrollabilityCache.NOT_DRAGGING; + return false; + } + + switch (action) { + case MotionEvent.ACTION_MOVE: + if (mScrollCache.mScrollBarDraggingState == ScrollabilityCache.NOT_DRAGGING) { + return false; + } + if (mScrollCache.mScrollBarDraggingState + == ScrollabilityCache.DRAGGING_VERTICAL_SCROLL_BAR) { + final Rect bounds = mScrollCache.mScrollBarBounds; + getVerticalScrollBarBounds(bounds); + final int range = computeVerticalScrollRange(); + final int offset = computeVerticalScrollOffset(); + final int extent = computeVerticalScrollExtent(); + + final int thumbLength = ScrollBarUtils.getThumbLength( + bounds.height(), bounds.width(), extent, range); + final int thumbOffset = ScrollBarUtils.getThumbOffset( + bounds.height(), thumbLength, extent, range, offset); + + final float diff = y - mScrollCache.mScrollBarDraggingPos; + final float maxThumbOffset = bounds.height() - thumbLength; + final float newThumbOffset = + Math.min(Math.max(thumbOffset + diff, 0.0f), maxThumbOffset); + final int height = getHeight(); + if (Math.round(newThumbOffset) != thumbOffset && maxThumbOffset > 0 + && height > 0 && extent > 0) { + final int newY = Math.round((range - extent) + / ((float)extent / height) * (newThumbOffset / maxThumbOffset)); + if (newY != getScrollY()) { + mScrollCache.mScrollBarDraggingPos = y; + setScrollY(newY); + } + } + return true; + } + if (mScrollCache.mScrollBarDraggingState + == ScrollabilityCache.DRAGGING_HORIZONTAL_SCROLL_BAR) { + final Rect bounds = mScrollCache.mScrollBarBounds; + getHorizontalScrollBarBounds(bounds); + final int range = computeHorizontalScrollRange(); + final int offset = computeHorizontalScrollOffset(); + final int extent = computeHorizontalScrollExtent(); + + final int thumbLength = ScrollBarUtils.getThumbLength( + bounds.width(), bounds.height(), extent, range); + final int thumbOffset = ScrollBarUtils.getThumbOffset( + bounds.width(), thumbLength, extent, range, offset); + + final float diff = x - mScrollCache.mScrollBarDraggingPos; + final float maxThumbOffset = bounds.width() - thumbLength; + final float newThumbOffset = + Math.min(Math.max(thumbOffset + diff, 0.0f), maxThumbOffset); + final int width = getWidth(); + if (Math.round(newThumbOffset) != thumbOffset && maxThumbOffset > 0 + && width > 0 && extent > 0) { + final int newX = Math.round((range - extent) + / ((float)extent / width) * (newThumbOffset / maxThumbOffset)); + if (newX != getScrollX()) { + mScrollCache.mScrollBarDraggingPos = x; + setScrollX(newX); + } + } + return true; + } + case MotionEvent.ACTION_DOWN: + if (isOnVerticalScrollbarThumb(x, y)) { + mScrollCache.mScrollBarDraggingState = + ScrollabilityCache.DRAGGING_VERTICAL_SCROLL_BAR; + mScrollCache.mScrollBarDraggingPos = y; + return true; + } + if (isOnHorizontalScrollbarThumb(x, y)) { + mScrollCache.mScrollBarDraggingState = + ScrollabilityCache.DRAGGING_HORIZONTAL_SCROLL_BAR; + mScrollCache.mScrollBarDraggingPos = x; + return true; + } + } + mScrollCache.mScrollBarDraggingState = ScrollabilityCache.NOT_DRAGGING; + return false; + } + + /** * Implement this method to handle touch screen motion events. * <p> * If this method is used to detect click actions, it is recommended that @@ -10717,7 +10908,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } - if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; @@ -14278,6 +14468,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + private void getHorizontalScrollBarBounds(Rect bounds) { + final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0; + final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled() + && !isVerticalScrollBarHidden(); + final int size = getHorizontalScrollbarHeight(); + final int verticalScrollBarGap = drawVerticalScrollBar ? + getVerticalScrollbarWidth() : 0; + final int width = mRight - mLeft; + final int height = mBottom - mTop; + bounds.top = mScrollY + height - size - (mUserPaddingBottom & inside); + bounds.left = mScrollX + (mPaddingLeft & inside); + bounds.right = mScrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap; + bounds.bottom = bounds.top + size; + } + + private void getVerticalScrollBarBounds(Rect bounds) { + final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0; + final int size = getVerticalScrollbarWidth(); + int verticalScrollbarPosition = mVerticalScrollbarPosition; + if (verticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT) { + verticalScrollbarPosition = isLayoutRtl() ? + SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT; + } + final int width = mRight - mLeft; + final int height = mBottom - mTop; + switch (verticalScrollbarPosition) { + default: + case SCROLLBAR_POSITION_RIGHT: + bounds.left = mScrollX + width - size - (mUserPaddingRight & inside); + break; + case SCROLLBAR_POSITION_LEFT: + bounds.left = mScrollX + (mUserPaddingLeft & inside); + break; + } + bounds.top = mScrollY + (mPaddingTop & inside); + bounds.right = bounds.left + size; + bounds.bottom = mScrollY + height - (mUserPaddingBottom & inside); + } + /** * <p>Request the drawing of the horizontal and the vertical scrollbar. The * scrollbars are painted only if they have been awakened first.</p> @@ -14325,80 +14554,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, cache.scrollBar.mutate().setAlpha(255); } - - final int viewFlags = mViewFlags; - - final boolean drawHorizontalScrollBar = - (viewFlags & SCROLLBARS_HORIZONTAL) == SCROLLBARS_HORIZONTAL; - final boolean drawVerticalScrollBar = - (viewFlags & SCROLLBARS_VERTICAL) == SCROLLBARS_VERTICAL - && !isVerticalScrollBarHidden(); + final boolean drawHorizontalScrollBar = isHorizontalScrollBarEnabled(); + final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled() + && !isVerticalScrollBarHidden(); if (drawVerticalScrollBar || drawHorizontalScrollBar) { - final int width = mRight - mLeft; - final int height = mBottom - mTop; - final ScrollBarDrawable scrollBar = cache.scrollBar; - final int scrollX = mScrollX; - final int scrollY = mScrollY; - final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0; - - int left; - int top; - int right; - int bottom; - if (drawHorizontalScrollBar) { - int size = scrollBar.getSize(false); - if (size <= 0) { - size = cache.scrollBarSize; - } - scrollBar.setParameters(computeHorizontalScrollRange(), computeHorizontalScrollOffset(), computeHorizontalScrollExtent(), false); - final int verticalScrollBarGap = drawVerticalScrollBar ? - getVerticalScrollbarWidth() : 0; - top = scrollY + height - size - (mUserPaddingBottom & inside); - left = scrollX + (mPaddingLeft & inside); - right = scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap; - bottom = top + size; - onDrawHorizontalScrollBar(canvas, scrollBar, left, top, right, bottom); + final Rect bounds = cache.mScrollBarBounds; + getHorizontalScrollBarBounds(bounds); + onDrawHorizontalScrollBar(canvas, scrollBar, bounds.left, bounds.top, + bounds.right, bounds.bottom); if (invalidate) { - invalidate(left, top, right, bottom); + invalidate(bounds); } } if (drawVerticalScrollBar) { - int size = scrollBar.getSize(true); - if (size <= 0) { - size = cache.scrollBarSize; - } - scrollBar.setParameters(computeVerticalScrollRange(), computeVerticalScrollOffset(), computeVerticalScrollExtent(), true); - int verticalScrollbarPosition = mVerticalScrollbarPosition; - if (verticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT) { - verticalScrollbarPosition = isLayoutRtl() ? - SCROLLBAR_POSITION_LEFT : SCROLLBAR_POSITION_RIGHT; - } - switch (verticalScrollbarPosition) { - default: - case SCROLLBAR_POSITION_RIGHT: - left = scrollX + width - size - (mUserPaddingRight & inside); - break; - case SCROLLBAR_POSITION_LEFT: - left = scrollX + (mUserPaddingLeft & inside); - break; - } - top = scrollY + (mPaddingTop & inside); - right = left + size; - bottom = scrollY + height - (mUserPaddingBottom & inside); - onDrawVerticalScrollBar(canvas, scrollBar, left, top, right, bottom); + final Rect bounds = cache.mScrollBarBounds; + getVerticalScrollBarBounds(bounds); + onDrawVerticalScrollBar(canvas, scrollBar, bounds.left, bounds.top, + bounds.right, bounds.bottom); if (invalidate) { - invalidate(left, top, right, bottom); + invalidate(bounds); } } } @@ -21338,6 +21523,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see PointerIcon */ public PointerIcon getPointerIcon(MotionEvent event, float x, float y) { + if (isDraggingScrollBar() || isOnScrollbarThumb(x, y)) { + return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_ARROW); + } return mPointerIcon; } @@ -22609,6 +22797,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private int mLastColor; + public final Rect mScrollBarBounds = new Rect(); + + public static final int NOT_DRAGGING = 0; + public static final int DRAGGING_VERTICAL_SCROLL_BAR = 1; + public static final int DRAGGING_HORIZONTAL_SCROLL_BAR = 2; + public int mScrollBarDraggingState = NOT_DRAGGING; + + public float mScrollBarDraggingPos = 0; + public ScrollabilityCache(ViewConfiguration configuration, View host) { fadingEdgeLength = configuration.getScaledFadingEdgeLength(); scrollBarSize = configuration.getScaledScrollBarSize(); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 27e2ea37e748..3fe6b8e2e3f8 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -59,6 +59,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; + import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; /** @@ -1651,6 +1652,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override public PointerIcon getPointerIcon(MotionEvent event, float x, float y) { + if (isOnScrollbarThumb(x, y) || isDraggingScrollBar()) { + return PointerIcon.getSystemIcon(mContext, PointerIcon.STYLE_ARROW); + } // Check what the child under the pointer says about the pointer. final int childrenCount = mChildrenCount; if (childrenCount != 0) { @@ -1974,7 +1978,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * hover exit event in {@link #onHoverEvent} and then the hovered child will * receive a hover enter event. * </p><p> - * The default implementation always returns false. + * The default implementation handles mouse hover on the scroll bars. * </p> * * @param event The motion event that describes the hover. @@ -1982,6 +1986,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * and prevent its children from receiving it. */ public boolean onInterceptHoverEvent(MotionEvent event) { + if (event.isFromSource(InputDevice.SOURCE_MOUSE)) { + final int action = event.getAction(); + final float x = event.getX(); + final float y = event.getY(); + if ((action == MotionEvent.ACTION_HOVER_MOVE + || action == MotionEvent.ACTION_HOVER_ENTER) && isOnScrollbar(x, y)) { + return true; + } + } return false; } @@ -2716,6 +2729,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * messages will be delivered here. */ public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.isFromSource(InputDevice.SOURCE_MOUSE) + && ev.getAction() == MotionEvent.ACTION_DOWN + && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) + && isOnScrollbarThumb(ev.getX(), ev.getY())) { + return true; + } return false; } diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index e9b123b5b9be..1962be86ffb1 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -266,14 +266,14 @@ public interface ViewParent { * intercept touch events. */ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept); - + /** * Called when a child of this group wants a particular rectangle to be * positioned onto the screen. {@link ViewGroup}s overriding this can trust * that: * <ul> * <li>child will be a direct child of this group</li> - * <li>rectangle will be in the child's coordinates</li> + * <li>rectangle will be in the child's content coordinates</li> * </ul> * * <p>{@link ViewGroup}s overriding this should uphold the contract:</p> diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 0d6f36226fa9..5d41477d3c0f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6729,16 +6729,19 @@ public final class ViewRootImpl implements ViewParent, @Override public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { + if (rectangle == null) { + return scrollToRectOrFocus(null, immediate); + } + rectangle.offset(child.getLeft() - child.getScrollX(), + child.getTop() - child.getScrollY()); final boolean scrolled = scrollToRectOrFocus(rectangle, immediate); - if (rectangle != null) { - mTempRect.set(rectangle); - mTempRect.offset(0, -mCurScrollY); - mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop); - try { - mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect); - } catch (RemoteException re) { - /* ignore */ - } + mTempRect.set(rectangle); + mTempRect.offset(0, -mCurScrollY); + mTempRect.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop); + try { + mWindowSession.onRectangleOnScreenRequested(mWindow, mTempRect); + } catch (RemoteException re) { + /* ignore */ } return scrolled; } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 6c2c956efa7d..9561f086ceb8 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -3701,6 +3701,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + /** @hide */ + @Override + protected boolean handleScrollBarDragging(MotionEvent event) { + // Doesn't support normal scroll bar dragging. Use FastScroller. + return false; + } + @Override public boolean onTouchEvent(MotionEvent ev) { if (!isEnabled()) { diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 4355eb3bcd69..67473c6a4521 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1082,6 +1082,20 @@ public class Editor { return true; } + private void startDragAndDrop() { + final int start = mTextView.getSelectionStart(); + final int end = mTextView.getSelectionEnd(); + CharSequence selectedText = mTextView.getTransformedText(start, end); + ClipData data = ClipData.newPlainText(null, selectedText); + DragLocalState localState = new DragLocalState(mTextView, start, end); + mTextView.startDragAndDrop(data, getTextThumbnailBuilder(selectedText), localState, + View.DRAG_FLAG_GLOBAL); + stopTextActionMode(); + if (hasSelectionController()) { + getSelectionController().resetTouchOffsets(); + } + } + public boolean performLongClick(boolean handled) { // Long press in empty space moves cursor and starts the insertion action mode. if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY) && @@ -1097,15 +1111,7 @@ public class Editor { if (!handled && mTextActionMode != null) { if (touchPositionIsInSelection()) { - // Start a drag - final int start = mTextView.getSelectionStart(); - final int end = mTextView.getSelectionEnd(); - CharSequence selectedText = mTextView.getTransformedText(start, end); - ClipData data = ClipData.newPlainText(null, selectedText); - DragLocalState localState = new DragLocalState(mTextView, start, end); - mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState, - View.DRAG_FLAG_GLOBAL); - stopTextActionMode(); + startDragAndDrop(); } else { stopTextActionMode(); selectCurrentWordAndStartDrag(); @@ -4925,6 +4931,14 @@ public class Editor { if (isMouse && !isDragAcceleratorActive()) { final int offset = mTextView.getOffsetForPosition(eventX, eventY); + if (mTextView.hasSelection() + && (!mHaventMovedEnoughToStartDrag || mStartOffset != offset) + && offset >= mTextView.getSelectionStart() + && offset <= mTextView.getSelectionEnd()) { + startDragAndDrop(); + break; + } + if (mStartOffset != offset) { // Start character based drag accelerator. if (mTextActionMode != null) { diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index ebc7eb3e3646..f16fdd657f84 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -454,6 +454,10 @@ public class HorizontalScrollView extends FrameLayout { return true; } + if (super.onInterceptTouchEvent(ev)) { + return true; + } + switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_MOVE: { /* diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java index 91d623216137..8880217d9461 100644 --- a/core/java/android/widget/ScrollBarDrawable.java +++ b/core/java/android/widget/ScrollBarDrawable.java @@ -16,6 +16,8 @@ package android.widget; +import com.android.internal.widget.ScrollBarUtils; + import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.PixelFormat; @@ -135,23 +137,15 @@ public class ScrollBarDrawable extends Drawable implements Drawable.Callback { } if (drawThumb) { - final int size = vertical ? r.height() : r.width(); + final int scrollBarLength = vertical ? r.height() : r.width(); final int thickness = vertical ? r.width() : r.height(); - final int minLength = thickness * 2; - - // Avoid the tiny thumb. - int length = Math.round((float) size * extent / range); - if (length < minLength) { - length = minLength; - } - - // Avoid the too-big thumb. - int offset = Math.round((float) (size - length) * mOffset / (range - extent)); - if (offset > size - length) { - offset = size - length; - } + final int thumbLength = + ScrollBarUtils.getThumbLength(scrollBarLength, thickness, extent, range); + final int thumbOffset = + ScrollBarUtils.getThumbOffset(scrollBarLength, thumbLength, extent, range, + mOffset); - drawThumb(canvas, r, offset, length, vertical); + drawThumb(canvas, r, thumbOffset, thumbLength, vertical); } } diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 78b931d94fd2..3f7a07bbd31a 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -489,6 +489,10 @@ public class ScrollView extends FrameLayout { return true; } + if (super.onInterceptTouchEvent(ev)) { + return true; + } + /* * Don't try to intercept touch if we can't scroll anyway. */ diff --git a/core/java/com/android/internal/widget/ScrollBarUtils.java b/core/java/com/android/internal/widget/ScrollBarUtils.java new file mode 100644 index 000000000000..0ae9f74167d5 --- /dev/null +++ b/core/java/com/android/internal/widget/ScrollBarUtils.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +public class ScrollBarUtils { + + public static int getThumbLength(int size, int thickness, int extent, int range) { + // Avoid the tiny thumb. + final int minLength = thickness * 2; + int length = Math.round((float) size * extent / range); + if (length < minLength) { + length = minLength; + } + return length; + } + + public static int getThumbOffset(int size, int thumbLength, int extent, int range, int offset) { + // Avoid the too-big thumb. + int thumbOffset = Math.round((float) (size - thumbLength) * offset / (range - extent)); + if (thumbOffset > size - thumbLength) { + thumbOffset = size - thumbLength; + } + return thumbOffset; + } +} diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 5a08887a7380..cf7978c12bfe 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -214,7 +214,7 @@ <!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm), visual voicemail code for T-Mobile: 122 --> - <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327|654)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" free="122|87902" /> + <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:567|578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" free="122|87902" /> <!-- Vietnam --> <shortcode country="vn" free="5001" /> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index c0453f8ac991..eb0075b6b0b7 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -53,6 +53,7 @@ <uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" /> <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" /> <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.INJECT_EVENTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_LOGS"/> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java index afd0bc4e5295..00df87d19b94 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java @@ -37,6 +37,7 @@ import static android.support.test.espresso.action.ViewActions.typeTextIntoFocus import static android.support.test.espresso.assertion.ViewAssertions.matches; import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; import com.android.frameworks.coretests.R; @@ -145,6 +146,40 @@ public class TextViewActivityMouseTest extends ActivityInstrumentationTestCase2< } @SmallTest + public void testDragAndDrop() throws Exception { + final String text = "abc def ghi."; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); + onView(withId(R.id.textview)).perform( + mouseDragOnText(text.indexOf("d"), text.indexOf("f") + 1)); + + onView(withId(R.id.textview)).perform( + mouseDragOnText(text.indexOf("e"), text.length())); + + onView(withId(R.id.textview)).check(matches(withText("abc ghi.def"))); + onView(withId(R.id.textview)).check(hasSelection("")); + assertNoSelectionHandles(); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length())); + } + + @SmallTest + public void testDragAndDrop_longClick() throws Exception { + final String text = "abc def ghi."; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); + onView(withId(R.id.textview)).perform( + mouseDragOnText(text.indexOf("d"), text.indexOf("f") + 1)); + + onView(withId(R.id.textview)).perform( + mouseLongClickAndDragOnText(text.indexOf("e"), text.length())); + + onView(withId(R.id.textview)).check(matches(withText("abc ghi.def"))); + onView(withId(R.id.textview)).check(hasSelection("")); + assertNoSelectionHandles(); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length())); + } + + @SmallTest public void testSelectTextByLongClick() throws Exception { final String helloWorld = "Hello world!"; onView(withId(R.id.textview)).perform(click()); diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java index edbfef9c82bc..5dae4a878bf4 100644 --- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java +++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java @@ -159,6 +159,22 @@ public class TextViewActivityTest extends ActivityInstrumentationTestCase2<TextV } @SmallTest + public void testDragAndDrop() throws Exception { + final String text = "abc def ghi."; + onView(withId(R.id.textview)).perform(click()); + onView(withId(R.id.textview)).perform(typeTextIntoFocusedView(text)); + onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("e"))); + + onView(withId(R.id.textview)).perform( + longPressAndDragOnText(text.indexOf("e"), text.length())); + + onView(withId(R.id.textview)).check(matches(withText("abc ghi.def"))); + onView(withId(R.id.textview)).check(hasSelection("")); + assertNoSelectionHandles(); + onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length())); + } + + @SmallTest public void testDoubleTapToSelect() throws Exception { final String helloWorld = "Hello SuetYi!"; onView(withId(R.id.textview)).perform(click()); diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl index 86c0e5dc920c..8ef5ca0f62d8 100644 --- a/media/java/android/media/tv/ITvInputClient.aidl +++ b/media/java/android/media/tv/ITvInputClient.aidl @@ -43,4 +43,10 @@ oneway interface ITvInputClient { void onTimeShiftStatusChanged(int status, int seq); void onTimeShiftStartPositionChanged(long timeMs, int seq); void onTimeShiftCurrentPositionChanged(long timeMs, int seq); + + // For the recording session + void onConnected(int seq); + void onRecordingStarted(int seq); + void onRecordingStopped(in Uri recordedProgramUri, int seq); + void onError(int error, int seq); } diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index f8057dbfc898..0febc162e5a7 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -55,7 +55,8 @@ interface ITvInputManager { void addBlockedRating(in String rating, int userId); void removeBlockedRating(in String rating, int userId); - void createSession(in ITvInputClient client, in String inputId, int seq, int userId); + void createSession(in ITvInputClient client, in String inputId, boolean isRecordingSession, + int seq, int userId); void releaseSession(in IBinder sessionToken, int userId); void setMainSession(in IBinder sessionToken, int userId); @@ -77,12 +78,18 @@ interface ITvInputManager { void unblockContent(in IBinder sessionToken, in String unblockedRating, int userId); + void timeShiftPlay(in IBinder sessionToken, in Uri recordedProgramUri, int userId); void timeShiftPause(in IBinder sessionToken, int userId); void timeShiftResume(in IBinder sessionToken, int userId); void timeShiftSeekTo(in IBinder sessionToken, long timeMs, int userId); void timeShiftSetPlaybackParams(in IBinder sessionToken, in PlaybackParams params, int userId); void timeShiftEnablePositionTracking(in IBinder sessionToken, boolean enable, int userId); + // For the recording session + void connect(in IBinder sessionToken, in Uri channelUri, in Bundle params, int userId); + void startRecording(in IBinder sessionToken, int userId); + void stopRecording(in IBinder sessionToken, int userId); + // For TV input hardware binding List<TvInputHardwareInfo> getHardwareList(); ITvInputHardware acquireTvInputHardware(int deviceId, in ITvInputHardwareCallback callback, diff --git a/media/java/android/media/tv/ITvInputManagerCallback.aidl b/media/java/android/media/tv/ITvInputManagerCallback.aidl index 67926807b156..3bf415ba4bc5 100644 --- a/media/java/android/media/tv/ITvInputManagerCallback.aidl +++ b/media/java/android/media/tv/ITvInputManagerCallback.aidl @@ -16,13 +16,18 @@ package android.media.tv; +import android.media.tv.TvInputInfo; + /** * Interface to receive callbacks from ITvInputManager regardless of sessions. * @hide */ oneway interface ITvInputManagerCallback { - void onInputStateChanged(in String inputId, int state); void onInputAdded(in String inputId); void onInputRemoved(in String inputId); void onInputUpdated(in String inputId); + + void onInputStateChanged(in String inputId, int state); + + void onTvInputInfoChanged(in String inputId, in TvInputInfo TvInputInfo); } diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl index 7a853d1caa62..bd0518493557 100644 --- a/media/java/android/media/tv/ITvInputService.aidl +++ b/media/java/android/media/tv/ITvInputService.aidl @@ -27,10 +27,11 @@ import android.view.InputChannel; * @hide */ oneway interface ITvInputService { - void registerCallback(ITvInputServiceCallback callback); + void registerCallback(in ITvInputServiceCallback callback); void unregisterCallback(in ITvInputServiceCallback callback); - void createSession(in InputChannel channel, ITvInputSessionCallback callback, + void createSession(in InputChannel channel, in ITvInputSessionCallback callback, in String inputId); + void createRecordingSession(in ITvInputSessionCallback callback, in String inputId); // For hardware TvInputService void notifyHardwareAdded(in TvInputHardwareInfo hardwareInfo); diff --git a/media/java/android/media/tv/ITvInputServiceCallback.aidl b/media/java/android/media/tv/ITvInputServiceCallback.aidl index 74ab56215ff2..9f13882685aa 100644 --- a/media/java/android/media/tv/ITvInputServiceCallback.aidl +++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl @@ -27,4 +27,6 @@ oneway interface ITvInputServiceCallback { void addHardwareTvInput(in int deviceId, in TvInputInfo inputInfo); void addHdmiTvInput(in int id, in TvInputInfo inputInfo); void removeTvInput(in String inputId); + + void setTvInputInfo(in String inputId, in TvInputInfo inputInfo); } diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index 6a06b8f19466..408a76277845 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -48,9 +48,16 @@ oneway interface ITvInputSession { void unblockContent(in String unblockedRating); + void timeShiftPlay(in Uri recordedProgramUri); void timeShiftPause(); void timeShiftResume(); void timeShiftSeekTo(long timeMs); void timeShiftSetPlaybackParams(in PlaybackParams params); void timeShiftEnablePositionTracking(boolean enable); + + // For the recording session + void connect(in Uri channelUri, in Bundle params); + void disconnect(); + void startRecording(); + void stopRecording(); } diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl index e93681086159..cb6a05e07b0d 100644 --- a/media/java/android/media/tv/ITvInputSessionCallback.aidl +++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl @@ -40,4 +40,10 @@ oneway interface ITvInputSessionCallback { void onTimeShiftStatusChanged(int status); void onTimeShiftStartPositionChanged(long timeMs); void onTimeShiftCurrentPositionChanged(long timeMs); + + // For the recording session + void onConnected(); + void onRecordingStarted(); + void onRecordingStopped(in Uri recordedProgramUri); + void onError(int error); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index f8c6f3fb7198..4ac58766ca94 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -59,20 +59,29 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand private static final int DO_RELAYOUT_OVERLAY_VIEW = 11; private static final int DO_REMOVE_OVERLAY_VIEW = 12; private static final int DO_UNBLOCK_CONTENT = 13; - private static final int DO_TIME_SHIFT_PAUSE = 14; - private static final int DO_TIME_SHIFT_RESUME = 15; - private static final int DO_TIME_SHIFT_SEEK_TO = 16; - private static final int DO_TIME_SHIFT_SET_PLAYBACK_PARAMS = 17; - private static final int DO_TIME_SHIFT_ENABLE_POSITION_TRACKING = 18; - + private static final int DO_TIME_SHIFT_PLAY = 14; + private static final int DO_TIME_SHIFT_PAUSE = 15; + private static final int DO_TIME_SHIFT_RESUME = 16; + private static final int DO_TIME_SHIFT_SEEK_TO = 17; + private static final int DO_TIME_SHIFT_SET_PLAYBACK_PARAMS = 18; + private static final int DO_TIME_SHIFT_ENABLE_POSITION_TRACKING = 19; + private static final int DO_CONNECT = 20; + private static final int DO_DISCONNECT = 21; + private static final int DO_START_RECORDING = 22; + private static final int DO_STOP_RECORDING = 23; + + private final boolean mIsRecordingSession; private final HandlerCaller mCaller; private TvInputService.Session mTvInputSessionImpl; + private TvInputService.RecordingSession mTvInputRecordingSessionImpl; + private InputChannel mChannel; private TvInputEventReceiver mReceiver; public ITvInputSessionWrapper(Context context, TvInputService.Session sessionImpl, InputChannel channel) { + mIsRecordingSession = false; mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */); mTvInputSessionImpl = sessionImpl; mChannel = channel; @@ -81,9 +90,19 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } } + public ITvInputSessionWrapper(Context context, + TvInputService.RecordingSession recordingSessionImpl) { + mIsRecordingSession = true; + mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */); + mTvInputRecordingSessionImpl = recordingSessionImpl; + } + @Override public void executeMessage(Message msg) { - if (mTvInputSessionImpl == null) { + if (!mIsRecordingSession && mTvInputSessionImpl == null) { + return; + } + if (mIsRecordingSession && mTvInputRecordingSessionImpl == null) { return; } @@ -138,7 +157,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } case DO_APP_PRIVATE_COMMAND: { SomeArgs args = (SomeArgs) msg.obj; - mTvInputSessionImpl.appPrivateCommand((String) args.arg1, (Bundle) args.arg2); + if (mIsRecordingSession) { + mTvInputRecordingSessionImpl.appPrivateCommand( + (String) args.arg1, (Bundle) args.arg2); + } else { + mTvInputSessionImpl.appPrivateCommand((String) args.arg1, (Bundle) args.arg2); + } args.recycle(); break; } @@ -160,6 +184,10 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.unblockContent((String) msg.obj); break; } + case DO_TIME_SHIFT_PLAY: { + mTvInputSessionImpl.timeShiftPlay((Uri) msg.obj); + break; + } case DO_TIME_SHIFT_PAUSE: { mTvInputSessionImpl.timeShiftPause(); break; @@ -180,6 +208,25 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mTvInputSessionImpl.timeShiftEnablePositionTracking((Boolean) msg.obj); break; } + case DO_CONNECT: { + SomeArgs args = (SomeArgs) msg.obj; + mTvInputRecordingSessionImpl.connect((Uri) args.arg1, (Bundle) args.arg2); + args.recycle(); + break; + } + case DO_DISCONNECT: { + mTvInputRecordingSessionImpl.disconnect(); + mTvInputRecordingSessionImpl = null; + break; + } + case DO_START_RECORDING: { + mTvInputRecordingSessionImpl.startRecording(); + break; + } + case DO_STOP_RECORDING: { + mTvInputRecordingSessionImpl.stopRecording(); + break; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); break; @@ -274,6 +321,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand } @Override + public void timeShiftPlay(Uri recordedProgramUri) { + mCaller.executeOrSendMessage(mCaller.obtainMessageO( + DO_TIME_SHIFT_PLAY, recordedProgramUri)); + } + + @Override public void timeShiftPause() { mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_TIME_SHIFT_PAUSE)); } @@ -300,6 +353,28 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand DO_TIME_SHIFT_ENABLE_POSITION_TRACKING, enable)); } + @Override + public void connect(Uri channelUri, Bundle params) { + // Clear the pending connect requests. + mCaller.removeMessages(DO_CONNECT); + mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_CONNECT, channelUri, params)); + } + + @Override + public void disconnect() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_DISCONNECT)); + } + + @Override + public void startRecording() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_START_RECORDING)); + } + + @Override + public void stopRecording() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_STOP_RECORDING)); + } + private final class TvInputEventReceiver extends InputEventReceiver { public TvInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java index 7cd086ecf14d..62a01dcf8808 100644 --- a/media/java/android/media/tv/TvContract.java +++ b/media/java/android/media/tv/TvContract.java @@ -54,6 +54,7 @@ public final class TvContract { private static final String PATH_CHANNEL = "channel"; private static final String PATH_PROGRAM = "program"; + private static final String PATH_RECORDED_PROGRAM = "recorded_program"; private static final String PATH_PASSTHROUGH = "passthrough"; /** @@ -273,6 +274,15 @@ public final class TvContract { } /** + * Builds a URI that points to a specific recorded program. + * + * @param recordedProgramId The ID of the recorded program to point to. + */ + public static final Uri buildRecordedProgramUri(long recordedProgramId) { + return ContentUris.withAppendedId(RecordedPrograms.CONTENT_URI, recordedProgramId); + } + + /** * Builds a URI that points to a specific program the user watched. * * @param watchedProgramId The ID of the watched program to point to. @@ -941,6 +951,8 @@ public final class TvContract { * * <p>This is a part of the channel URI and matches to {@link BaseColumns#_ID}. * + * <p>This is a required field. + * * <p>Type: INTEGER (long) */ public static final String COLUMN_CHANNEL_ID = "channel_id"; @@ -1336,6 +1348,382 @@ public final class TvContract { } /** + * Column definitions for the recorded TV programs table. + * + * <p>By default, the query results will be sorted by {@link #COLUMN_START_TIME_UTC_MILLIS} in + * ascending order. + */ + public static final class RecordedPrograms implements BaseTvColumns { + + /** The content:// style URI for this table. */ + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + + PATH_RECORDED_PROGRAM); + + /** The MIME type of a directory of recorded TV programs. */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/recorded_program"; + + /** The MIME type of a single recorded TV program. */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/recorded_program"; + + /** + * The ID of the TV channel that provided this recorded TV program. + * + * <p>This is a part of the channel URI and matches to {@link BaseColumns#_ID}. + * + * <p>This is a required field. + * + * <p>Type: INTEGER (long) + * @see Programs#COLUMN_CHANNEL_ID + */ + public static final String COLUMN_CHANNEL_ID = Programs.COLUMN_CHANNEL_ID; + + /** + * The title of this recorded TV program. + * + * <p>If this recorded program is an episodic TV show, it is recommended that the title is + * the series title and its related fields ({@link #COLUMN_SEASON_NUMBER}, + * {@link #COLUMN_EPISODE_NUMBER}, and {@link #COLUMN_EPISODE_TITLE}) are filled in. + * + * <p>Type: TEXT + * @see Programs#COLUMN_TITLE + */ + public static final String COLUMN_TITLE = Programs.COLUMN_TITLE; + + /** + * The season number of this recorded TV program for episodic TV shows. + * + * <p>Can be empty. + * + * <p>Type: INTEGER + * @see Programs#COLUMN_SEASON_NUMBER + */ + public static final String COLUMN_SEASON_NUMBER = Programs.COLUMN_SEASON_NUMBER; + + /** + * The episode number of this recorded TV program for episodic TV shows. + * + * <p>Can be empty. + * + * <p>Type: INTEGER + * @see Programs#COLUMN_EPISODE_NUMBER + */ + public static final String COLUMN_EPISODE_NUMBER = Programs.COLUMN_EPISODE_NUMBER; + + /** + * The episode title of this recorded TV program for episodic TV shows. + * + * <p>Can be empty. + * + * <p>Type: TEXT + * @see Programs#COLUMN_EPISODE_TITLE + */ + public static final String COLUMN_EPISODE_TITLE = Programs.COLUMN_EPISODE_TITLE; + + /** + * The start time of the original TV program, in milliseconds since the epoch. + * + * <p>Type: INTEGER (long) + * @see Programs#COLUMN_START_TIME_UTC_MILLIS + */ + public static final String COLUMN_START_TIME_UTC_MILLIS = + Programs.COLUMN_START_TIME_UTC_MILLIS; + + /** + * The end time of the original TV program, in milliseconds since the epoch. + * + * <p>Type: INTEGER (long) + * @see Programs#COLUMN_END_TIME_UTC_MILLIS + */ + public static final String COLUMN_END_TIME_UTC_MILLIS = Programs.COLUMN_END_TIME_UTC_MILLIS; + + /** + * The comma-separated genre string of this recorded 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, leave empty. + * + * <p>Type: TEXT + * @see Programs#COLUMN_BROADCAST_GENRE + */ + public static final String COLUMN_BROADCAST_GENRE = Programs.COLUMN_BROADCAST_GENRE; + + /** + * The comma-separated canonical genre string of this recorded TV program. + * + * <p>Canonical genres are defined in {@link Programs.Genres}. Use + * {@link Programs.Genres#encode Genres.encode()} to create a text that can be stored in + * this column. Use {@link Programs.Genres#decode Genres.decode()} to get the canonical + * genre strings from the text stored in this column. + * + * <p>Type: TEXT + * @see Programs#COLUMN_CANONICAL_GENRE + * @see Programs.Genres + */ + public static final String COLUMN_CANONICAL_GENRE = Programs.COLUMN_CANONICAL_GENRE; + + /** + * The short description of this recorded TV program that is displayed to the user by + * default. + * + * <p>It is recommended to limit the length of the descriptions to 256 characters. + * + * <p>Type: TEXT + * @see Programs#COLUMN_SHORT_DESCRIPTION + */ + public static final String COLUMN_SHORT_DESCRIPTION = Programs.COLUMN_SHORT_DESCRIPTION; + + /** + * The detailed, lengthy description of this recorded 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_SHORT_DESCRIPTION}. + * + * <p>Type: TEXT + * @see Programs#COLUMN_LONG_DESCRIPTION + */ + public static final String COLUMN_LONG_DESCRIPTION = Programs.COLUMN_LONG_DESCRIPTION; + + /** + * The width of the video for this recorded TV program, in the unit of pixels. + * + * <p>Together with {@link #COLUMN_VIDEO_HEIGHT} this is used to determine the video + * resolution of the current recorded TV program. Can be empty if it is not known or the + * recorded program does not convey any video. + * + * <p>Type: INTEGER + * @see Programs#COLUMN_VIDEO_WIDTH + */ + public static final String COLUMN_VIDEO_WIDTH = Programs.COLUMN_VIDEO_WIDTH; + + /** + * The height of the video for this recorded TV program, in the unit of pixels. + * + * <p>Together with {@link #COLUMN_VIDEO_WIDTH} this is used to determine the video + * resolution of the current recorded TV program. Can be empty if it is not known or the + * recorded program does not convey any video. + * + * <p>Type: INTEGER + * @see Programs#COLUMN_VIDEO_HEIGHT + */ + public static final String COLUMN_VIDEO_HEIGHT = Programs.COLUMN_VIDEO_HEIGHT; + + /** + * The comma-separated audio languages of this recorded TV program. + * + * <p>This is used to describe available audio languages included in the recorded program. + * Use either ISO 639-1 or 639-2/T codes. + * + * <p>Type: TEXT + * @see Programs#COLUMN_AUDIO_LANGUAGE + */ + public static final String COLUMN_AUDIO_LANGUAGE = Programs.COLUMN_AUDIO_LANGUAGE; + + /** + * The comma-separated content ratings of this recorded TV program. + * + * <p>This is used to describe the content rating(s) of this recorded program. Each + * comma-separated content rating sub-string should be generated by calling + * {@link TvContentRating#flattenToString}. Note that in most cases the recorded program + * content is rated by a single rating system, thus resulting in a corresponding single + * sub-string that does not require comma separation and multiple sub-strings appear only + * when the recorded program content is rated by two or more content rating systems. If any + * of those ratings is specified as "blocked rating" in the user's parental control + * settings, the TV input service should block the current content and wait for the signal + * that it is okay to unblock. + * + * <p>Type: TEXT + * @see Programs#COLUMN_CONTENT_RATING + */ + public static final String COLUMN_CONTENT_RATING = Programs.COLUMN_CONTENT_RATING; + + /** + * The URI for the poster art of this recorded TV program. + * + * <p>The data in the column must be a URL, or a URI in one of the following formats: + * + * <ul> + * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li> + * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}) + * </li> + * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li> + * </ul> + * + * <p>Can be empty. + * + * <p>Type: TEXT + * @see Programs#COLUMN_POSTER_ART_URI + */ + public static final String COLUMN_POSTER_ART_URI = Programs.COLUMN_POSTER_ART_URI; + + /** + * The URI for the thumbnail of this recorded TV program. + * + * <p>The system can generate a thumbnail from the poster art if this column is not + * specified. Thus it is not necessary for TV input services to include a thumbnail if it is + * just a scaled image of the poster art. + * + * <p>The data in the column must be a URL, or a URI in one of the following formats: + * + * <ul> + * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li> + * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE}) + * </li> + * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li> + * </ul> + * + * <p>Can be empty. + * + * <p>Type: TEXT + * @see Programs#COLUMN_THUMBNAIL_URI + */ + public static final String COLUMN_THUMBNAIL_URI = Programs.COLUMN_THUMBNAIL_URI; + + /** + * The flag indicating whether this recorded TV program is searchable or not. + * + * <p>The columns of searchable recorded programs can be read by other applications that + * have proper permission. Care must be taken not to open sensitive data. + * + * <p>A value of 1 indicates that the recorded program is searchable and its columns can be + * read by other applications, a value of 0 indicates that the recorded program is hidden + * and its columns can be read only by the package that owns the recorded program and the + * system. If not specified, this value is set to 1 (searchable) by default. + * + * <p>Type: INTEGER (boolean) + * @see Programs#COLUMN_SEARCHABLE + */ + public static final String COLUMN_SEARCHABLE = Programs.COLUMN_SEARCHABLE; + + /** + * The URI of the recording data for this recorded program. + * + * <p>Together with {@link #COLUMN_RECORDING_DATA_BYTES}, applications can use this + * information to manage recording storage. The URI should indicate a file or directory with + * the scheme {@link android.content.ContentResolver#SCHEME_FILE}. + * + * <p>Type: TEXT + * @see #COLUMN_RECORDING_DATA_BYTES + */ + public static final String COLUMN_RECORDING_DATA_URI = "recording_data_uri"; + + /** + * The data size (in bytes) for this recorded program. + * + * <p>Together with {@link #COLUMN_RECORDING_DATA_URI}, applications can use this + * information to manage recording storage. + * + * <p>Type: INTEGER (long) + * @see #COLUMN_RECORDING_DATA_URI + */ + public static final String COLUMN_RECORDING_DATA_BYTES = "recording_data_bytes"; + + /** + * The duration (in milliseconds) of this recorded program. + * + * <p>The actual duration of the recorded program can differ from the one calculated by + * {@link #COLUMN_END_TIME_UTC_MILLIS} - {@link #COLUMN_START_TIME_UTC_MILLIS} as program + * recording can be interrupted in the middle for some reason, resulting in a partially + * recorded program, which is still playable. + * + * <p>Type: INTEGER + */ + public static final String COLUMN_RECORDING_DURATION_MILLIS = "recording_duration_millis"; + + /** + * The expiration time for this recorded program, in milliseconds since the epoch. + * + * <p>Recorded TV programs do not expire by default unless explicitly requested by the user + * or the user allows applications to delete them in order to free up disk space for future + * recording. However, some TV content can have expiration date set by the content provider + * when recorded. This field is used to indicate such a restriction. + * + * <p>Can be empty. + * + * <p>Type: INTEGER (long) + */ + public static final String COLUMN_RECORDING_EXPIRE_TIME_UTC_MILLIS = + "recording_expire_time_utc_millis"; + + + /** + * Internal data used by individual TV input services. + * + * <p>This is internal to the provider that inserted it, and should not be decoded by other + * apps. + * + * <p>Type: BLOB + * @see Programs#COLUMN_INTERNAL_PROVIDER_DATA + */ + public static final String COLUMN_INTERNAL_PROVIDER_DATA = + Programs.COLUMN_INTERNAL_PROVIDER_DATA; + + /** + * Internal integer flag used by individual TV input services. + * + * <p>This is internal to the provider that inserted it, and should not be decoded by other + * apps. + * + * <p>Type: INTEGER + * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG1 + */ + public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = + Programs.COLUMN_INTERNAL_PROVIDER_FLAG1; + + /** + * Internal integer flag used by individual TV input services. + * + * <p>This is internal to the provider that inserted it, and should not be decoded by other + * apps. + * + * <p>Type: INTEGER + * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG2 + */ + public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = + Programs.COLUMN_INTERNAL_PROVIDER_FLAG2; + + /** + * Internal integer flag used by individual TV input services. + * + * <p>This is internal to the provider that inserted it, and should not be decoded by other + * apps. + * + * <p>Type: INTEGER + * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG3 + */ + public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = + Programs.COLUMN_INTERNAL_PROVIDER_FLAG3; + + /** + * Internal integer flag used by individual TV input services. + * + * <p>This is internal to the provider that inserted it, and should not be decoded by other + * apps. + * + * <p>Type: INTEGER + * @see Programs#COLUMN_INTERNAL_PROVIDER_FLAG4 + */ + public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = + Programs.COLUMN_INTERNAL_PROVIDER_FLAG4; + + /** + * 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>Type: INTEGER + * @see Programs#COLUMN_VERSION_NUMBER + */ + public static final String COLUMN_VERSION_NUMBER = Programs.COLUMN_VERSION_NUMBER; + + private RecordedPrograms() {} + } + + /** * Column definitions for the TV programs that the user watched. Applications do not have access * to this table. * @@ -1376,6 +1764,8 @@ public final class TvContract { /** * The ID of the TV channel that provides this TV program. * + * <p>This is a required field. + * * <p>Type: INTEGER (long) */ public static final String COLUMN_CHANNEL_ID = "channel_id"; diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java index a3d748efafd2..396023079709 100644 --- a/media/java/android/media/tv/TvInputInfo.java +++ b/media/java/android/media/tv/TvInputInfo.java @@ -419,6 +419,27 @@ public final class TvInputInfo implements Parcelable { } /** + * Returns the number of tuners this TV input has. + * + * <p>This method is valid only for the input of type {@link #TYPE_TUNER}. + * + * <p>Tuners correspond to physical/logical resources that allow reception of TV signal. Having + * <i>N</i> tuners means that the TV input is capable of receiving <i>N</i> different channels + * concurrently. + * + */ + public int getTunerCount() { + return mType == TYPE_TUNER ? 1 : 0; + } + + /** + * Returns {@code true} if this TV input can record TV programs, {@code false} otherwise. + */ + public boolean canRecord() { + return false; + } + + /** * Returns the HDMI device information of this TV input. * @hide */ diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 6a13f826949a..f1de8fdc1817 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -16,6 +16,7 @@ package android.media.tv; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -44,6 +45,8 @@ import android.view.View; import com.android.internal.util.Preconditions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; @@ -126,6 +129,35 @@ public final class TvInputManager { public static final long TIME_SHIFT_INVALID_TIME = Long.MIN_VALUE; /** + * RecordingError when a requested operation cannot be completed due to a problem that does not + * fit under any other error code. + */ + public static final int RECORDING_ERROR_UNKNOWN = 0; + + /** + * RecordingError when an attempt to connect to a recording session has failed or the + * established connection has been disconnected without a known reason. + */ + public static final int RECORDING_ERROR_CONNECTION_FAILED = 1; + + /** + * RecordingError when recording cannot proceed due to insufficient storage space. + */ + public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 2; + + /** + * RecordingError when recording cannot proceed because the required recording resource is not + * able to be allocated. + */ + public static final int RECORDING_ERROR_RESOURCE_BUSY = 3; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({RECORDING_ERROR_UNKNOWN, RECORDING_ERROR_CONNECTION_FAILED, + RECORDING_ERROR_INSUFFICIENT_SPACE, RECORDING_ERROR_RESOURCE_BUSY}) + public @interface RecordingError {} + + /** * The TV input is connected. * * <p>This state indicates that a source device is connected to the input port and is in the @@ -416,6 +448,39 @@ public final class TvInputManager { */ public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) { } + + /** + * This is called when a recording session initiated by a call to {@link + * TvRecordingClient#connect(String, Uri)} has been established. + */ + void onConnected(Session session) { + } + + /** + * This is called when TV program recording on the current channel has started. + * + * @param session A {@link TvInputManager.Session} associated with this callback. + */ + void onRecordingStarted(Session session) { + } + + /** + * This is called when TV program recording on the current channel has stopped. The passed + * URI contains information about the new recorded program. + * + * @param recordedProgramUri The URI for the new recorded program. + * @see android.media.tv.TvContract.RecordedPrograms + **/ + void onRecordingStopped(Session session, Uri recordedProgramUri) { + } + + /** + * This is called when an issue has occurred before or during recording. + * + * @param error The error code. + */ + void onError(Session session, @TvInputManager.RecordingError int error) { + } } private static final class SessionCallbackRecord { @@ -565,6 +630,46 @@ public final class TvInputManager { } }); } + + // For the recording session only + void postConnected() { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onConnected(mSession); + } + }); + } + + // For the recording session only + void postRecordingStarted() { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onRecordingStarted(mSession); + } + }); + } + + // For the recording session only + void postRecordingStopped(final Uri recordedProgramUri) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onRecordingStopped(mSession, recordedProgramUri); + } + }); + } + + // For the recording session only + void postError(final int error) { + mHandler.post(new Runnable() { + @Override + public void run() { + mSessionCallback.onError(mSession, error); + } + }); + } } /** @@ -574,7 +679,7 @@ public final class TvInputManager { /** * This is called when the state of a given TV input is changed. * - * @param inputId The id of the TV input. + * @param inputId The ID of the TV input. * @param state State of the TV input. The value is one of the following: * <ul> * <li>{@link TvInputManager#INPUT_STATE_CONNECTED} @@ -591,7 +696,7 @@ public final class TvInputManager { * <p>Normally it happens when the user installs a new TV input package that implements * {@link TvInputService} interface. * - * @param inputId The id of the TV input. + * @param inputId The ID of the TV input. */ public void onInputAdded(String inputId) { } @@ -602,7 +707,7 @@ public final class TvInputManager { * <p>Normally it happens when the user uninstalls the previously installed TV input * package. * - * @param inputId The id of the TV input. + * @param inputId The ID of the TV input. */ public void onInputRemoved(String inputId) { } @@ -613,12 +718,21 @@ public final class TvInputManager { * <p>Normally it happens when a previously installed TV input package is re-installed or * the media on which a newer version of the package exists becomes available/unavailable. * - * @param inputId The id of the TV input. + * @param inputId The ID of the TV input. * @hide */ @SystemApi public void onInputUpdated(String inputId) { } + + /** + * This is called when the information about a given TV input is changed. + * + * @param inputId The ID of the TV input. + * @param inputInfo TvInputInfo object that contains the information about the TV input. + */ + public void onTvInputInfoChanged(String inputId, TvInputInfo inputInfo) { + } } private static final class TvInputCallbackRecord { @@ -634,38 +748,47 @@ public final class TvInputManager { return mCallback; } - public void postInputStateChanged(final String inputId, final int state) { + public void postInputAdded(final String inputId) { mHandler.post(new Runnable() { @Override public void run() { - mCallback.onInputStateChanged(inputId, state); + mCallback.onInputAdded(inputId); } }); } - public void postInputAdded(final String inputId) { + public void postInputRemoved(final String inputId) { mHandler.post(new Runnable() { @Override public void run() { - mCallback.onInputAdded(inputId); + mCallback.onInputRemoved(inputId); } }); } - public void postInputRemoved(final String inputId) { + public void postInputUpdated(final String inputId) { mHandler.post(new Runnable() { @Override public void run() { - mCallback.onInputRemoved(inputId); + mCallback.onInputUpdated(inputId); } }); } - public void postInputUpdated(final String inputId) { + public void postInputStateChanged(final String inputId, final int state) { mHandler.post(new Runnable() { @Override public void run() { - mCallback.onInputUpdated(inputId); + mCallback.onInputStateChanged(inputId, state); + } + }); + } + + public void postTvInputInfoChanged(final String inputId, final TvInputInfo inputInfo) { + mHandler.post(new Runnable() { + @Override + public void run() { + mCallback.onTvInputInfoChanged(inputId, inputInfo); } }); } @@ -876,19 +999,57 @@ public final class TvInputManager { record.postTimeShiftCurrentPositionChanged(timeMs); } } - }; - ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() { + @Override - public void onInputStateChanged(String inputId, int state) { - synchronized (mLock) { - mStateMap.put(inputId, state); - for (TvInputCallbackRecord record : mCallbackRecords) { - record.postInputStateChanged(inputId, state); + public void onConnected(int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postConnected(); + } + } + + @Override + public void onRecordingStarted(int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postRecordingStarted(); + } + } + + @Override + public void onRecordingStopped(Uri recordedProgramUri, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; } + record.postRecordingStopped(recordedProgramUri); } } @Override + public void onError(int error, int seq) { + synchronized (mSessionCallbackRecordMap) { + SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq); + if (record == null) { + Log.e(TAG, "Callback not found for seq " + seq); + return; + } + record.postError(error); + } + } + }; + ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() { + @Override public void onInputAdded(String inputId) { synchronized (mLock) { mStateMap.put(inputId, INPUT_STATE_CONNECTED); @@ -916,6 +1077,25 @@ public final class TvInputManager { } } } + + @Override + public void onInputStateChanged(String inputId, int state) { + synchronized (mLock) { + mStateMap.put(inputId, state); + for (TvInputCallbackRecord record : mCallbackRecords) { + record.postInputStateChanged(inputId, state); + } + } + } + + @Override + public void onTvInputInfoChanged(String inputId, TvInputInfo inputInfo) { + synchronized (mLock) { + for (TvInputCallbackRecord record : mCallbackRecords) { + record.postTvInputInfoChanged(inputId, inputInfo); + } + } + } }; try { if (mService != null) { @@ -972,7 +1152,7 @@ public final class TvInputManager { * <li>{@link #INPUT_STATE_DISCONNECTED} * </ul> * - * @param inputId The id of the TV input. + * @param inputId The ID of the TV input. * @throws IllegalArgumentException if the argument is {@code null}. */ public int getInputState(@NonNull String inputId) { @@ -1139,7 +1319,7 @@ public final class TvInputManager { * <p>The number of sessions that can be created at the same time is limited by the capability * of the given TV input. * - * @param inputId The id of the TV input. + * @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. * @hide @@ -1147,6 +1327,28 @@ public final class TvInputManager { @SystemApi public void createSession(@NonNull String inputId, @NonNull final SessionCallback callback, @NonNull Handler handler) { + createSessionInternal(inputId, false, callback, handler); + } + + /** + * Creates a recording {@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. + * + * @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. + * @hide + */ + @SystemApi + public void createRecordingSession(@NonNull String inputId, + @NonNull final SessionCallback callback, @NonNull Handler handler) { + createSessionInternal(inputId, true, callback, handler); + } + + private void createSessionInternal(String inputId, boolean isRecordingSession, + SessionCallback callback, Handler handler) { Preconditions.checkNotNull(inputId); Preconditions.checkNotNull(callback); Preconditions.checkNotNull(handler); @@ -1155,7 +1357,7 @@ public final class TvInputManager { int seq = mNextSeq++; mSessionCallbackRecordMap.put(seq, record); try { - mService.createSession(mClient, inputId, seq, mUserId); + mService.createSession(mClient, inputId, isRecordingSession, seq, mUserId); } catch (RemoteException e) { throw new RuntimeException(e); } @@ -1171,7 +1373,7 @@ public final class TvInputManager { * here. This method is designed to be used with {@link #captureFrame} in * capture scenarios specifically and not suitable for any other use. * - * @param inputId the id of the TV input. + * @param inputId The ID of the TV input. * @return List of {@link TvStreamConfig} which is available for capturing * of the given TV input. * @hide @@ -1188,7 +1390,7 @@ public final class TvInputManager { /** * Take a snapshot of the given TV input into the provided Surface. * - * @param inputId the id of the TV input. + * @param inputId The ID of the TV input. * @param surface the {@link Surface} to which the snapshot is captured. * @param config the {@link TvStreamConfig} which is used for capturing. * @return true when the {@link Surface} is ready to be captured. @@ -1607,7 +1809,7 @@ public final class TvInputManager { * Returns the selected track for a given type. Returns {@code null} if the information is * not available or any of the tracks for the given type is not selected. * - * @return the ID of the selected track. + * @return The ID of the selected track. * @see #selectTrack */ @Nullable @@ -1697,6 +1899,21 @@ public final class TvInputManager { } /** + * Plays a given recorded TV program. + */ + void timeShiftPlay(Uri recordedProgramUri) { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.timeShiftPlay(mToken, recordedProgramUri, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** * Pauses the playback. Call {@link #timeShiftResume()} to restart the playback. */ void timeShiftPause() { @@ -1782,6 +1999,62 @@ public final class TvInputManager { } /** + * Connects to a given channel for TV program recording. + */ + void connect(Uri channelUri) { + connect(channelUri, null); + } + + /** + * Tunes to a given channel. + * + * @param channelUri The URI of a channel. + * @param params Extra parameters. + */ + void connect(@NonNull Uri channelUri, Bundle params) { + Preconditions.checkNotNull(channelUri); + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.connect(mToken, channelUri, params, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Starts TV program recording for the current recording session. + */ + void startRecording() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.startRecording(mToken, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** + * Stops TV program recording for the current recording session. + */ + void stopRecording() { + if (mToken == null) { + Log.w(TAG, "The session has been already released"); + return; + } + try { + mService.stopRecording(mToken, mUserId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle) * TvInputService.Session.appPrivateCommand()} on the current TvView. * diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 053d43b6b69b..6d9b1ad339a4 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -16,6 +16,7 @@ package android.media.tv; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -136,6 +137,18 @@ public abstract class TvInputService extends Service { } @Override + public void createRecordingSession(ITvInputSessionCallback cb, String inputId) { + if (cb == null) { + return; + } + SomeArgs args = SomeArgs.obtain(); + args.arg1 = cb; + args.arg2 = inputId; + mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_RECORDING_SESSION, args) + .sendToTarget(); + } + + @Override public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) { mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_TV_INPUT, hardwareInfo).sendToTarget(); @@ -174,6 +187,17 @@ public abstract class TvInputService extends Service { public abstract Session onCreateSession(String inputId); /** + * Returns a concrete implementation of {@link RecordingSession}. + * + * <p>May return {@code null} if this TV input service fails to create a recording session for + * some reason. + * + * @param inputId The ID of the TV input associated with the recording session. + */ + @Nullable + public abstract RecordingSession onCreateRecordingSession(String inputId); + + /** * Returns a new {@link TvInputInfo} object if this service is responsible for * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of * ignoring all hardware input. @@ -229,6 +253,25 @@ public abstract class TvInputService extends Service { return null; } + + /** + * Sets the TvInputInfo for this TV input. + * + * <p>The system service automatically creates the TvInputInfo for each TV input based on + * information collected from the AndroidManifest.xml, thus it is not necessary to call this + * method unless the TV input has additional information to pass such as ability to record and + * tuner count. + * + * @param inputId The ID of the TV input. + * @param inputInfo The TvInputInfo object that contains that new information. + */ + public final void setTvInputInfo(String inputId, TvInputInfo inputInfo) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = inputId; + args.arg2 = inputInfo; + mServiceHandler.obtainMessage(ServiceHandler.DO_SET_TV_INPUT_INFO, args).sendToTarget(); + } + private boolean isPassthroughInput(String inputId) { if (mTvInputManager == null) { mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); @@ -322,7 +365,7 @@ public abstract class TvInputService extends Service { @SystemApi public void notifySessionEvent(@NonNull final String eventType, final Bundle eventArgs) { Preconditions.checkNotNull(eventType); - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { @Override public void run() { try { @@ -347,7 +390,8 @@ public abstract class TvInputService extends Service { * @param channelUri The URI of the new channel. */ public void notifyChannelRetuned(final Uri channelUri) { - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread @Override public void run() { try { @@ -387,7 +431,8 @@ public abstract class TvInputService extends Service { // TODO: Validate the track list. final List<TvTrackInfo> tracksCopy = new ArrayList<>(tracks); - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread @Override public void run() { try { @@ -417,7 +462,8 @@ public abstract class TvInputService extends Service { * @see #onSelectTrack */ public void notifyTrackSelected(final int type, final String trackId) { - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread @Override public void run() { try { @@ -443,7 +489,8 @@ public abstract class TvInputService extends Service { * @see #notifyVideoUnavailable */ public void notifyVideoAvailable() { - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread @Override public void run() { try { @@ -478,7 +525,8 @@ public abstract class TvInputService extends Service { || reason > TvInputManager.VIDEO_UNAVAILABLE_REASON_END) { throw new IllegalArgumentException("Unknown reason: " + reason); } - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread @Override public void run() { try { @@ -518,7 +566,8 @@ public abstract class TvInputService extends Service { * @see TvInputManager */ public void notifyContentAllowed() { - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread @Override public void run() { try { @@ -562,7 +611,8 @@ public abstract class TvInputService extends Service { */ public void notifyContentBlocked(@NonNull final TvContentRating rating) { Preconditions.checkNotNull(rating); - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread @Override public void run() { try { @@ -603,7 +653,8 @@ public abstract class TvInputService extends Service { * </ul> */ public void notifyTimeShiftStatusChanged(final int status) { - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread @Override public void run() { try { @@ -619,7 +670,8 @@ public abstract class TvInputService extends Service { } private void notifyTimeShiftStartPositionChanged(final long timeMs) { - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread @Override public void run() { try { @@ -635,7 +687,8 @@ public abstract class TvInputService extends Service { } private void notifyTimeShiftCurrentPositionChanged(final long timeMs) { - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread @Override public void run() { try { @@ -665,7 +718,8 @@ public abstract class TvInputService extends Service { if (left > right || top > bottom) { throw new IllegalArgumentException("Invalid parameter"); } - executeOrPostRunnable(new Runnable() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread @Override public void run() { try { @@ -858,13 +912,28 @@ public abstract class TvInputService extends Service { } /** + * Called when the application requests to play a given recorded TV program. + * + * @param recordedProgramUri The URI of a recorded TV program. + * @see #onTimeShiftResume() + * @see #onTimeShiftPause() + * @see #onTimeShiftSeekTo(long) + * @see #onTimeShiftSetPlaybackParams(PlaybackParams) + * @see #onTimeShiftGetStartPosition() + * @see #onTimeShiftGetCurrentPosition() + */ + public void onTimeShiftPlay(Uri recordedProgramUri) { + } + + /** * Called when the application requests to pause playback. * - * @see #onTimeShiftResume - * @see #onTimeShiftSeekTo - * @see #onTimeShiftSetPlaybackParams - * @see #onTimeShiftGetStartPosition - * @see #onTimeShiftGetCurrentPosition + * @see #onTimeShiftPlay(Uri) + * @see #onTimeShiftResume() + * @see #onTimeShiftSeekTo(long) + * @see #onTimeShiftSetPlaybackParams(PlaybackParams) + * @see #onTimeShiftGetStartPosition() + * @see #onTimeShiftGetCurrentPosition() */ public void onTimeShiftPause() { } @@ -872,11 +941,12 @@ public abstract class TvInputService extends Service { /** * Called when the application requests to resume playback. * - * @see #onTimeShiftPause - * @see #onTimeShiftSeekTo - * @see #onTimeShiftSetPlaybackParams - * @see #onTimeShiftGetStartPosition - * @see #onTimeShiftGetCurrentPosition + * @see #onTimeShiftPlay(Uri) + * @see #onTimeShiftPause() + * @see #onTimeShiftSeekTo(long) + * @see #onTimeShiftSetPlaybackParams(PlaybackParams) + * @see #onTimeShiftGetStartPosition() + * @see #onTimeShiftGetCurrentPosition() */ public void onTimeShiftResume() { } @@ -888,11 +958,12 @@ public abstract class TvInputService extends Service { * not in the range. * * @param timeMs The time position to seek to, in milliseconds since the epoch. - * @see #onTimeShiftResume - * @see #onTimeShiftPause - * @see #onTimeShiftSetPlaybackParams - * @see #onTimeShiftGetStartPosition - * @see #onTimeShiftGetCurrentPosition + * @see #onTimeShiftPlay(Uri) + * @see #onTimeShiftResume() + * @see #onTimeShiftPause() + * @see #onTimeShiftSetPlaybackParams(PlaybackParams) + * @see #onTimeShiftGetStartPosition() + * @see #onTimeShiftGetCurrentPosition() */ public void onTimeShiftSeekTo(long timeMs) { } @@ -905,11 +976,12 @@ public abstract class TvInputService extends Service { * parameters previously set. * * @param params The playback params. - * @see #onTimeShiftResume - * @see #onTimeShiftPause - * @see #onTimeShiftSeekTo - * @see #onTimeShiftGetStartPosition - * @see #onTimeShiftGetCurrentPosition + * @see #onTimeShiftPlay(Uri) + * @see #onTimeShiftResume() + * @see #onTimeShiftPause() + * @see #onTimeShiftSeekTo(long) + * @see #onTimeShiftGetStartPosition() + * @see #onTimeShiftGetCurrentPosition() */ public void onTimeShiftSetPlaybackParams(PlaybackParams params) { } @@ -925,11 +997,12 @@ public abstract class TvInputService extends Service { * seek to, thus failure to notifying its change immediately might result in bad experience * where the application allows the user to seek to an invalid time position. * - * @see #onTimeShiftResume - * @see #onTimeShiftPause - * @see #onTimeShiftSeekTo - * @see #onTimeShiftSetPlaybackParams - * @see #onTimeShiftGetCurrentPosition + * @see #onTimeShiftPlay(Uri) + * @see #onTimeShiftResume() + * @see #onTimeShiftPause() + * @see #onTimeShiftSeekTo(long) + * @see #onTimeShiftSetPlaybackParams(PlaybackParams) + * @see #onTimeShiftGetCurrentPosition() */ public long onTimeShiftGetStartPosition() { return TvInputManager.TIME_SHIFT_INVALID_TIME; @@ -944,11 +1017,12 @@ public abstract class TvInputService extends Service { * playback position reported by {@link #onTimeShiftGetStartPosition}. Failure to notifying * the correct current position might lead to bad user experience. * - * @see #onTimeShiftResume - * @see #onTimeShiftPause - * @see #onTimeShiftSeekTo - * @see #onTimeShiftSetPlaybackParams - * @see #onTimeShiftGetStartPosition + * @see #onTimeShiftPlay(Uri) + * @see #onTimeShiftResume() + * @see #onTimeShiftPause() + * @see #onTimeShiftSeekTo(long) + * @see #onTimeShiftSetPlaybackParams(PlaybackParams) + * @see #onTimeShiftGetStartPosition() */ public long onTimeShiftGetCurrentPosition() { return TvInputManager.TIME_SHIFT_INVALID_TIME; @@ -1263,6 +1337,14 @@ public abstract class TvInputService extends Service { } /** + * Calls {@link #onTimeShiftPlay(Uri)}. + */ + void timeShiftPlay(Uri recordedProgramUri) { + mCurrentPositionMs = 0; + onTimeShiftPlay(recordedProgramUri); + } + + /** * Calls {@link #onTimeShiftPause}. */ void timeShiftPause() { @@ -1385,7 +1467,7 @@ public abstract class TvInputService extends Service { } } - private void executeOrPostRunnable(Runnable action) { + private void executeOrPostRunnableOnMainThread(Runnable action) { synchronized(mLock) { if (mSessionCallback == null) { // The session is not initialized yet. @@ -1449,6 +1531,267 @@ public abstract class TvInputService extends Service { } /** + * Base class for derived classes to implement to provide a TV input recording session. + */ + public abstract static class RecordingSession { + final Handler mHandler; + + private final Object mLock = new Object(); + // @GuardedBy("mLock") + private ITvInputSessionCallback mSessionCallback; + // @GuardedBy("mLock") + private final List<Runnable> mPendingActions = new ArrayList<>(); + + /** + * Creates a new Recording Session for TV program recording. + * + * @param context The context of the application + */ + public RecordingSession(Context context) { + mHandler = new Handler(context.getMainLooper()); + } + + /** + * Informs the application that recording session has been connected. + */ + public void notifyConnected() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifyConnected"); + if (mSessionCallback != null) { + mSessionCallback.onConnected(); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyConnected", e); + } + } + }); + } + + /** + * Informs the application that recording has started. + */ + public void notifyRecordingStarted() { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifyRecordingStarted"); + if (mSessionCallback != null) { + mSessionCallback.onRecordingStarted(); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyRecordingStarted", e); + } + } + }); + } + + /** + * Informs the application that recording has stopped successfully. Each TV input service + * should create a new data entry in the recorded programs table upon completion of the + * recording and send its URI. + * + * @param recordedProgramUri The URI of the new recorded program. + */ + public void notifyRecordingStopped(final Uri recordedProgramUri) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifyRecordingStopped"); + if (mSessionCallback != null) { + mSessionCallback.onRecordingStopped(recordedProgramUri); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyRecordingStopped", e); + } + } + }); + } + + /** + * Sends an error to the application at any moment. + * + * @param error The error code. Should be one of the followings. + * <ul> + * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN} + * <li>{@link TvInputManager#RECORDING_ERROR_CONNECTION_FAILED} + * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE} + * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY} + * </ul> + */ + public void notifyError(@TvInputManager.RecordingError final int error) { + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifyError"); + if (mSessionCallback != null) { + mSessionCallback.onError(error); + } + } catch (RemoteException e) { + Log.w(TAG, "error in notifyError", e); + } + } + }); + } + + /** + * Dispatches an event to the application using this recording session. + * + * @param eventType The type of the event. + * @param eventArgs Optional arguments of the event. + * @hide + */ + @SystemApi + public void notifySessionEvent(@NonNull final String eventType, final Bundle eventArgs) { + Preconditions.checkNotNull(eventType); + executeOrPostRunnableOnMainThread(new Runnable() { + @MainThread + @Override + public void run() { + try { + if (DEBUG) Log.d(TAG, "notifySessionEvent(" + eventType + ")"); + if (mSessionCallback != null) { + mSessionCallback.onSessionEvent(eventType, eventArgs); + } + } catch (RemoteException e) { + Log.w(TAG, "error in sending event (event=" + eventType + ")", e); + } + } + }); + } + + /** + * Called when the recording session is connected. + * + * @param channelUri The URI of the channel. + */ + public abstract void onConnect(Uri channelUri); + + /** + * Called when the recording session is connected. + * + * @param channelUri The URI of the channel. + * @param params Extra parameters. + * @hide + */ + @SystemApi + public void onConnect(Uri channelUri, Bundle params) { + onConnect(channelUri); + } + + /** + * Called when the application requests to disconnect the current recording session. + */ + public abstract void onDisconnect(); + + /** + * Called when the application requests to start recording. Recording must start + * immediately. + * + * <p>The session must call either {@link #notifyRecordingStarted()} or + * {@link #notifyError(int)}}. + */ + public abstract void onStartRecording(); + + /** + * Called when the application requests to stop recording. Recording must stop immediately. + * + * <p>The session must call either {@link #notifyRecordingStopped(Uri)} or + * {@link #notifyError(int)}}. + */ + public abstract void onStopRecording(); + + /** + * Processes a private command sent from the application to the TV input. This can be used + * to provide domain-specific features that are only known between certain TV inputs and + * their clients. + * + * @param action Name of the command to be performed. This <em>must</em> be a scoped name, + * i.e. prefixed with a package name you own, so that different developers will + * not create conflicting commands. + * @param data Any data to include with the command. + * @hide + */ + @SystemApi + public void onAppPrivateCommand(@NonNull String action, Bundle data) { + } + + /** + * Calls {@link #onConnect(Uri, Bundle)}. + * + */ + void connect(Uri channelUri, Bundle params) { + onConnect(channelUri, params); + } + + /** + * Calls {@link #onDisconnect()}. + * + */ + void disconnect() { + onDisconnect(); + } + + /** + * Calls {@link #onStartRecording()}. + * + */ + void startRecording() { + onStartRecording(); + } + + /** + * Calls {@link #onStopRecording()}. + * + */ + void stopRecording() { + onStopRecording(); + } + + /** + * Calls {@link #onAppPrivateCommand(String, Bundle)}. + */ + void appPrivateCommand(String action, Bundle data) { + onAppPrivateCommand(action, data); + } + + private void initialize(ITvInputSessionCallback callback) { + synchronized(mLock) { + mSessionCallback = callback; + for (Runnable runnable : mPendingActions) { + runnable.run(); + } + mPendingActions.clear(); + } + } + + private void executeOrPostRunnableOnMainThread(Runnable action) { + synchronized(mLock) { + if (mSessionCallback == null) { + // The session is not initialized yet. + mPendingActions.add(action); + } else { + if (mHandler.getLooper().isCurrentThread()) { + action.run(); + } else { + // Posts the runnable if this is not called from the main thread + mHandler.post(action); + } + } + } + } + } + + /** * Base class for a TV input session which represents an external device connected to a * hardware TV input. * @@ -1588,10 +1931,12 @@ public abstract class TvInputService extends Service { private final class ServiceHandler extends Handler { private static final int DO_CREATE_SESSION = 1; private static final int DO_NOTIFY_SESSION_CREATED = 2; - private static final int DO_ADD_HARDWARE_TV_INPUT = 3; - private static final int DO_REMOVE_HARDWARE_TV_INPUT = 4; - private static final int DO_ADD_HDMI_TV_INPUT = 5; - private static final int DO_REMOVE_HDMI_TV_INPUT = 6; + private static final int DO_CREATE_RECORDING_SESSION = 3; + private static final int DO_ADD_HARDWARE_TV_INPUT = 4; + private static final int DO_REMOVE_HARDWARE_TV_INPUT = 5; + private static final int DO_ADD_HDMI_TV_INPUT = 6; + private static final int DO_REMOVE_HDMI_TV_INPUT = 7; + private static final int DO_SET_TV_INPUT_INFO = 8; private void broadcastAddHardwareTvInput(int deviceId, TvInputInfo inputInfo) { int n = mCallbacks.beginBroadcast(); @@ -1629,6 +1974,18 @@ public abstract class TvInputService extends Service { mCallbacks.finishBroadcast(); } + private void broadcastSetTvInputInfo(String inputId, TvInputInfo inputInfo) { + int n = mCallbacks.beginBroadcast(); + for (int i = 0; i < n; ++i) { + try { + mCallbacks.getBroadcastItem(i).setTvInputInfo(inputId, inputInfo); + } catch (RemoteException e) { + Log.e(TAG, "error in broadcastSetTvInputInfo", e); + } + } + mCallbacks.finishBroadcast(); + } + @Override public final void handleMessage(Message msg) { switch (msg.what) { @@ -1704,6 +2061,31 @@ public abstract class TvInputService extends Service { args.recycle(); return; } + case DO_CREATE_RECORDING_SESSION: { + SomeArgs args = (SomeArgs) msg.obj; + ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg1; + String inputId = (String) args.arg2; + args.recycle(); + RecordingSession recordingSessionImpl = onCreateRecordingSession(inputId); + if (recordingSessionImpl == null) { + try { + // Failed to create a recording session. + cb.onSessionCreated(null, null); + } catch (RemoteException e) { + Log.e(TAG, "error in onSessionCreated", e); + } + return; + } + ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this, + recordingSessionImpl); + try { + cb.onSessionCreated(stub, null); + } catch (RemoteException e) { + Log.e(TAG, "error in onSessionCreated", e); + } + recordingSessionImpl.initialize(cb); + return; + } case DO_ADD_HARDWARE_TV_INPUT: { TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj; TvInputInfo inputInfo = onHardwareAdded(hardwareInfo); @@ -1736,6 +2118,16 @@ public abstract class TvInputService extends Service { } return; } + case DO_SET_TV_INPUT_INFO: { + SomeArgs args = (SomeArgs) msg.obj; + String inputId = (String) args.arg1; + TvInputInfo inputInfo = (TvInputInfo) args.arg2; + if (inputInfo != null) { + broadcastSetTvInputInfo(inputId, inputInfo); + } + args.recycle(); + return; + } default: { Log.w(TAG, "Unhandled message code: " + msg.what); return; diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java new file mode 100644 index 000000000000..865e00021d05 --- /dev/null +++ b/media/java/android/media/tv/TvRecordingClient.java @@ -0,0 +1,352 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.tv; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import java.util.ArrayDeque; +import java.util.Queue; + +/** + * The public interface object used to interact with a specific TV input service for TV program + * recording. + */ +public class TvRecordingClient { + private static final String TAG = "TvRecordingClient"; + private static final boolean DEBUG = false; + + private final RecordingCallback mCallback; + private final Handler mHandler; + + private final TvInputManager mTvInputManager; + private TvInputManager.Session mSession; + private MySessionCallback mSessionCallback; + + private final Queue<Pair<String, Bundle>> mPendingAppPrivateCommands = new ArrayDeque<>(); + + /** + * Creates a new TvRecordingClient object. + * + * @param context The application context to create the TvRecordingClient with. + * @param tag A short name for debugging purposes. + * @param callback The callback to receive recording status changes. + * @param handler The handler to invoke the callback on. + */ + public TvRecordingClient(Context context, String tag, @NonNull RecordingCallback callback, + Handler handler) { + mCallback = callback; + mHandler = handler == null ? new Handler(Looper.getMainLooper()) : handler; + mTvInputManager = (TvInputManager) context.getSystemService(Context.TV_INPUT_SERVICE); + } + + /** + * Connects to a given input for TV program recording. This will create a new recording session + * from the TV input and establishes the connection between the application and the session. + * + * <p>The recording session will respond by calling + * {@link RecordingCallback#onConnected()} or {@link RecordingCallback#onError(int)}. + * + * @param inputId The ID of the TV input for the given channel. + * @param channelUri The URI of a channel. + */ + public void connect(String inputId, Uri channelUri) { + connect(inputId, channelUri, null); + } + + /** + * Connects to a given input for TV program recording. This will create a new recording session + * from the TV input and establishes the connection between the application and the session. + * + * <p>The recording session will respond by calling + * {@link RecordingCallback#onConnected()} or {@link RecordingCallback#onError(int)}. + * + * @param inputId The ID of the TV input for the given channel. + * @param channelUri The URI of a channel. + * @param params Extra parameters. + * @hide + */ + @SystemApi + public void connect(String inputId, Uri channelUri, Bundle params) { + if (DEBUG) Log.d(TAG, "connect(" + channelUri + ")"); + if (TextUtils.isEmpty(inputId)) { + throw new IllegalArgumentException("inputId cannot be null or an empty string"); + } + if (mSessionCallback != null && TextUtils.equals(mSessionCallback.mInputId, inputId)) { + if (mSession != null) { + mSession.connect(channelUri, params); + } else { + mSessionCallback.mChannelUri = channelUri; + mSessionCallback.mConnectionParams = params; + } + } else { + resetInternal(); + mSessionCallback = new MySessionCallback(inputId, channelUri, params); + if (mTvInputManager != null) { + mTvInputManager.createRecordingSession(inputId, mSessionCallback, mHandler); + } + } + } + + /** + * Disconnects the established connection between the application and the recording session. + * + * <p>The recording session will respond by calling + * {@link RecordingCallback#onDisconnected()} or {@link RecordingCallback#onError(int)}. + */ + public void disconnect() { + if (DEBUG) Log.d(TAG, "disconnect()"); + resetInternal(); + } + + private void resetInternal() { + mSessionCallback = null; + mPendingAppPrivateCommands.clear(); + if (mSession != null) { + mSession.release(); + mSession = null; + } + } + + /** + * Starts TV program recording for the current recording session. It is expected that recording + * starts immediately after calling this method. + * + * <p>The recording session will respond by calling + * {@link RecordingCallback#onRecordingStarted()} or {@link RecordingCallback#onError(int)}. + */ + public void startRecording() { + if (mSession != null) { + mSession.startRecording(); + } + } + + /** + * Stops TV program recording for the current recording session. It is expected that recording + * stops immediately after calling this method. + * + * <p>The recording session will respond by calling + * {@link RecordingCallback#onRecordingStopped(Uri)} or {@link RecordingCallback#onError(int)}. + */ + public void stopRecording() { + if (mSession != null) { + mSession.stopRecording(); + } + } + + /** + * Calls {@link TvInputService.RecordingSession#appPrivateCommand(String, Bundle) + * TvInputService.RecordingSession.appPrivateCommand()} on the current TvView. + * + * @param action The name of the private command to send. This <em>must</em> be a scoped name, + * i.e. prefixed with a package name you own, so that different developers will not + * create conflicting commands. + * @param data An optional bundle to send with the command. + * @hide + */ + @SystemApi + public void sendAppPrivateCommand(@NonNull String action, Bundle data) { + if (TextUtils.isEmpty(action)) { + throw new IllegalArgumentException("action cannot be null or an empty string"); + } + if (mSession != null) { + mSession.sendAppPrivateCommand(action, data); + } else { + Log.w(TAG, "sendAppPrivateCommand - session not yet created (action \"" + action + + "\" pending)"); + mPendingAppPrivateCommands.add(Pair.create(action, data)); + } + } + + /** + * Callback used to receive various status updates on the + * {@link android.media.tv.TvInputService.RecordingSession} + */ + public class RecordingCallback { + /** + * This is called when a recording session initiated by a call to + * {@link #connect(String, Uri)} has been established. + */ + public void onConnected() { + } + + /** + * This is called when the established connection between the application and the recording + * session has been disconnected. Disconnection can be initiated either by an explicit + * request (i.e. a call to {@link #disconnect()} or by an error on the TV input service + * side. + */ + public void onDisconnected() { + } + + /** + * This is called when TV program recording on the current channel has started. + */ + public void onRecordingStarted() { + } + + /** + * This is called when TV program recording on the current channel has stopped. The passed + * URI contains information about the new recorded program. + * + * @param recordedProgramUri The URI for the new recorded program. + * @see android.media.tv.TvContract.RecordedPrograms + */ + public void onRecordingStopped(Uri recordedProgramUri) { + } + + /** + * This is called when an issue has occurred before or during recording. If the TV input + * service cannot proceed recording due to this error, a call to {@link #onDisconnected()} + * is expected to follow. + * + * @param error The error code. Should be one of the followings. + * <ul> + * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN} + * <li>{@link TvInputManager#RECORDING_ERROR_CONNECTION_FAILED} + * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE} + * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY} + * </ul> + */ + public void onError(@TvInputManager.RecordingError int error) { + } + + /** + * This is invoked when a custom event from the bound TV input is sent to this client. + * + * @param inputId The ID of the TV input bound to this client. + * @param eventType The type of the event. + * @param eventArgs Optional arguments of the event. + * @hide + */ + @SystemApi + public void onEvent(String inputId, String eventType, Bundle eventArgs) { + } + } + + private class MySessionCallback extends TvInputManager.SessionCallback { + final String mInputId; + Uri mChannelUri; + Bundle mConnectionParams; + + MySessionCallback(String inputId, Uri channelUri, Bundle connectionParams) { + mInputId = inputId; + mChannelUri = channelUri; + mConnectionParams = connectionParams; + } + + @Override + public void onSessionCreated(TvInputManager.Session session) { + if (DEBUG) { + Log.d(TAG, "onSessionCreated()"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onSessionCreated - session already created"); + // This callback is obsolete. + if (session != null) { + session.release(); + } + return; + } + mSession = session; + if (session != null) { + // Sends the pending app private commands. + for (Pair<String, Bundle> command : mPendingAppPrivateCommands) { + mSession.sendAppPrivateCommand(command.first, command.second); + } + mPendingAppPrivateCommands.clear(); + mSession.connect(mChannelUri, mConnectionParams); + } else { + mSessionCallback = null; + mCallback.onError(TvInputManager.RECORDING_ERROR_CONNECTION_FAILED); + } + } + + @Override + public void onSessionReleased(TvInputManager.Session session) { + if (DEBUG) { + Log.d(TAG, "onSessionReleased()"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onSessionReleased - session not created"); + return; + } + mSessionCallback = null; + mSession = null; + mCallback.onDisconnected(); + } + + @Override + public void onRecordingStarted(TvInputManager.Session session) { + if (DEBUG) { + Log.d(TAG, "onRecordingStarted()"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onRecordingStarted - session not created"); + return; + } + mCallback.onRecordingStarted(); + } + + @Override + public void onRecordingStopped(TvInputManager.Session session, Uri recordedProgramUri) { + if (DEBUG) { + Log.d(TAG, "onRecordingStopped()"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onRecordingStopped - session not created"); + return; + } + mCallback.onRecordingStopped(recordedProgramUri); + } + + @Override + public void onError(TvInputManager.Session session, int error) { + if (DEBUG) { + Log.d(TAG, "onError()"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onError - session not created"); + return; + } + mCallback.onError(error); + } + + @Override + public void onSessionEvent(TvInputManager.Session session, String eventType, + Bundle eventArgs) { + if (DEBUG) { + Log.d(TAG, "onSessionEvent(" + eventType + ")"); + } + if (this != mSessionCallback) { + Log.w(TAG, "onSessionEvent - session not created"); + return; + } + if (mCallback != null) { + mCallback.onEvent(mInputId, eventType, eventArgs); + } + } + } +} diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 003a2741e557..0132d2438470 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -448,6 +448,37 @@ public class TvView extends ViewGroup { } /** + * Plays a given recorded TV program. + * + * @param inputId The ID of the TV input that created the given recorded program. + * @param recordedProgramUri The URI of a recorded program. + */ + public void timeShiftPlay(String inputId, Uri recordedProgramUri) { + if (DEBUG) Log.d(TAG, "timeShiftPlay(" + recordedProgramUri + ")"); + if (TextUtils.isEmpty(inputId)) { + throw new IllegalArgumentException("inputId cannot be null or an empty string"); + } + synchronized (sMainTvViewLock) { + if (sMainTvView.get() == null) { + sMainTvView = new WeakReference<>(this); + } + } + if (mSessionCallback != null && TextUtils.equals(mSessionCallback.mInputId, inputId)) { + if (mSession != null) { + mSession.timeShiftPlay(recordedProgramUri); + } else { + mSessionCallback.mRecordedProgramUri = recordedProgramUri; + } + } else { + resetInternal(); + mSessionCallback = new MySessionCallback(inputId, recordedProgramUri); + if (mTvInputManager != null) { + mTvInputManager.createSession(inputId, mSessionCallback, mHandler); + } + } + } + + /** * Pauses playback. No-op if it is already paused. Call {@link #timeShiftResume} to resume. */ public void timeShiftPause() { @@ -994,6 +1025,7 @@ public class TvView extends ViewGroup { final String mInputId; Uri mChannelUri; Bundle mTuneParams; + Uri mRecordedProgramUri; MySessionCallback(String inputId, Uri channelUri, Bundle tuneParams) { mInputId = inputId; @@ -1001,6 +1033,11 @@ public class TvView extends ViewGroup { mTuneParams = tuneParams; } + MySessionCallback(String inputId, Uri recordedProgramUri) { + mInputId = inputId; + mRecordedProgramUri = recordedProgramUri; + } + @Override public void onSessionCreated(Session session) { if (DEBUG) { @@ -1043,7 +1080,11 @@ public class TvView extends ViewGroup { if (mCaptionEnabled != null) { mSession.setCaptionEnabled(mCaptionEnabled); } - mSession.tune(mChannelUri, mTuneParams); + if (mChannelUri != null) { + mSession.tune(mChannelUri, mTuneParams); + } else { + mSession.timeShiftPlay(mRecordedProgramUri); + } ensurePositionTracking(); } else { mSessionCallback = null; diff --git a/packages/SettingsLib/res/values/attrs.xml b/packages/SettingsLib/res/values/attrs.xml index 46267a218a3f..15b2a978a04a 100644 --- a/packages/SettingsLib/res/values/attrs.xml +++ b/packages/SettingsLib/res/values/attrs.xml @@ -18,4 +18,9 @@ <declare-styleable name="RestrictedPreference"> <attr name="userRestriction" format="string"/> </declare-styleable> -</resources>
\ No newline at end of file + <declare-styleable name="WifiEncryptionState"> + <attr name="state_encrypted" format="boolean" /> + </declare-styleable> + <attr name="wifi_signal" format="reference" /> + +</resources> diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index 9a1d6a404b08..811751cd00a5 100644 --- a/packages/SettingsLib/res/values/dimens.xml +++ b/packages/SettingsLib/res/values/dimens.xml @@ -35,4 +35,7 @@ <!-- Lock icon for preferences locked by admin --> <dimen name="restricted_lock_icon_size">16dp</dimen> <dimen name="restricted_lock_icon_padding">4dp</dimen> + + <dimen name="wifi_preference_badge_padding">8dip</dimen> + </resources> diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java new file mode 100644 index 000000000000..284827b57929 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settingslib.wifi; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.StateListDrawable; +import android.net.wifi.WifiConfiguration; +import android.os.Looper; +import android.os.UserHandle; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceViewHolder; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.widget.TextView; + +import com.android.settingslib.R; + +public class AccessPointPreference extends Preference { + + private static final int[] STATE_SECURED = { + R.attr.state_encrypted + }; + private static final int[] STATE_NONE = {}; + + private static int[] wifi_signal_attributes = { R.attr.wifi_signal }; + + private final StateListDrawable mWifiSld; + private final int mBadgePadding; + private final UserBadgeCache mBadgeCache; + + private TextView mTitleView; + private boolean mForSavedNetworks = false; + private AccessPoint mAccessPoint; + private Drawable mBadge; + private int mLevel; + private CharSequence mContentDescription; + + static final int[] WIFI_CONNECTION_STRENGTH = { + R.string.accessibility_wifi_one_bar, + R.string.accessibility_wifi_two_bars, + R.string.accessibility_wifi_three_bars, + R.string.accessibility_wifi_signal_full + }; + + // Used for dummy pref. + public AccessPointPreference(Context context, AttributeSet attrs) { + super(context, attrs); + mWifiSld = null; + mBadgePadding = 0; + mBadgeCache = null; + } + + public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, + boolean forSavedNetworks) { + super(context); + mBadgeCache = cache; + mAccessPoint = accessPoint; + mForSavedNetworks = forSavedNetworks; + mAccessPoint.setTag(this); + mLevel = -1; + + mWifiSld = (StateListDrawable) context.getTheme() + .obtainStyledAttributes(wifi_signal_attributes).getDrawable(0); + + // Distance from the end of the title at which this AP's user badge should sit. + mBadgePadding = context.getResources() + .getDimensionPixelSize(R.dimen.wifi_preference_badge_padding); + refresh(); + } + + public AccessPoint getAccessPoint() { + return mAccessPoint; + } + + @Override + public void onBindViewHolder(final PreferenceViewHolder view) { + super.onBindViewHolder(view); + if (mAccessPoint == null) { + // Used for dummy pref. + return; + } + Drawable drawable = getIcon(); + if (drawable != null) { + drawable.setLevel(mLevel); + } + + mTitleView = (TextView) view.findViewById(com.android.internal.R.id.title); + if (mTitleView != null) { + // Attach to the end of the title view + mTitleView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, mBadge, null); + mTitleView.setCompoundDrawablePadding(mBadgePadding); + } + view.itemView.setContentDescription(mContentDescription); + } + + protected void updateIcon(int level, Context context) { + if (level == -1) { + setIcon(null); + } else { + if (getIcon() == null) { + // To avoid a drawing race condition, we first set the state (SECURE/NONE) and then + // set the icon (drawable) to that state's drawable. + // If sld is null then we are indexing and therefore do not have access to + // (nor need to display) the drawable. + if (mWifiSld != null) { + mWifiSld.setState((mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) + ? STATE_SECURED + : STATE_NONE); + Drawable drawable = mWifiSld.getCurrent(); + if (!mForSavedNetworks) { + setIcon(drawable); + } else { + setIcon(null); + } + } + } + } + } + + protected void updateBadge(Context context) { + WifiConfiguration config = mAccessPoint.getConfig(); + if (config != null) { + // Fetch badge (may be null) + // Get the badge using a cache since the PM will ask the UserManager for the list + // of profiles every time otherwise. + mBadge = mBadgeCache.getUserBadge(config.creatorUid); + } + } + + /** + * Updates the title and summary; may indirectly call notifyChanged(). + */ + public void refresh() { + if (mForSavedNetworks) { + setTitle(mAccessPoint.getConfigName()); + } else { + setTitle(mAccessPoint.getSsid()); + } + + final Context context = getContext(); + int level = mAccessPoint.getLevel(); + if (level != mLevel) { + mLevel = level; + updateIcon(mLevel, context); + notifyChanged(); + } + updateBadge(context); + + setSummary(mForSavedNetworks ? mAccessPoint.getSavedNetworkSummary() + : mAccessPoint.getSettingsSummary()); + + mContentDescription = getTitle(); + if (getSummary() != null) { + mContentDescription = TextUtils.concat(mContentDescription, ",", getSummary()); + } + if (level >= 0 && level < WIFI_CONNECTION_STRENGTH.length) { + mContentDescription = TextUtils.concat(mContentDescription, ",", + getContext().getString(WIFI_CONNECTION_STRENGTH[level])); + } + } + + @Override + protected void notifyChanged() { + if (Looper.getMainLooper() != Looper.myLooper()) { + // Let our BG thread callbacks call setTitle/setSummary. + postNotifyChanged(); + } else { + super.notifyChanged(); + } + } + + public void onLevelChanged() { + postNotifyChanged(); + } + + private void postNotifyChanged() { + if (mTitleView != null) { + mTitleView.post(mNotifyChanged); + } // Otherwise we haven't been bound yet, and don't need to update. + } + + private final Runnable mNotifyChanged = new Runnable() { + @Override + public void run() { + notifyChanged(); + } + }; + + public static class UserBadgeCache { + private final SparseArray<Drawable> mBadges = new SparseArray<>(); + private final PackageManager mPm; + + public UserBadgeCache(PackageManager pm) { + mPm = pm; + } + + private Drawable getUserBadge(int userId) { + int index = mBadges.indexOfKey(userId); + if (index < 0) { + Drawable badge = mPm.getUserBadgeForDensity(new UserHandle(userId), 0 /* dpi */); + mBadges.put(userId, badge); + return badge; + } + return mBadges.valueAt(index); + } + } +} diff --git a/packages/SystemUI/res/drawable/ic_data_saver.xml b/packages/SystemUI/res/drawable/ic_data_saver.xml new file mode 100644 index 000000000000..426238c05f4b --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_data_saver.xml @@ -0,0 +1,28 @@ +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData=" + M9.0,16.0l2.0,0.0L11.0,8.0L9.0,8.0l0.0,8.0z + m3.0,-14.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0z + m0.0,18.0c-4.41,0.0 -8.0,-3.59 -8.0,-8.0s3.59,-8.0 8.0,-8.0 8.0,3.59 8.0,8.0 -3.59,8.0 -8.0,8.0z + m1.0,-4.0l2.0,0.0l0.0,-8.0l-2.0,0.0l0.0,8.0z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_data_saver_off.xml b/packages/SystemUI/res/drawable/ic_data_saver_off.xml new file mode 100644 index 000000000000..0713548fcd4d --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_data_saver_off.xml @@ -0,0 +1,28 @@ +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#4DFFFFFF" + android:pathData=" + M9.0,16.0l2.0,0.0L11.0,8.0L9.0,8.0l0.0,8.0z + m3.0,-14.0C6.48,2.0 2.0,6.48 2.0,12.0s4.48,10.0 10.0,10.0 10.0,-4.48 10.0,-10.0S17.52,2.0 12.0,2.0z + m0.0,18.0c-4.41,0.0 -8.0,-3.59 -8.0,-8.0s3.59,-8.0 8.0,-8.0 8.0,3.59 8.0,8.0 -3.59,8.0 -8.0,8.0z + m1.0,-4.0l2.0,0.0l0.0,-8.0l-2.0,0.0l0.0,8.0z"/> +</vector> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 814e7eae0474..faf36eca176f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1333,4 +1333,13 @@ <!-- Explanation of the status bar section of the tuner [CHAR LIMIT=NONE] --> <string name="tuner_status_bar_explanation">Enable or disable icons from being shown in the status bar.</string> + <!-- Label for quick settings tile for data saver [CHAR LIMIT=30] --> + <string name="data_saver">Data Saver</string> + + <!-- Accessibility description for data saver being on [CHAR LIMIT=NONE] --> + <string name="accessibility_data_saver_on">Data Saver is on</string> + + <!-- Accessibility description for data saver being off [CHAR LIMIT=NONE] --> + <string name="accessibility_data_saver_off">Data Saver is off</string> + </resources> diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index 6e22dde542d0..543a2f32b090 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -73,7 +73,7 @@ public class TileAdapter extends BaseAdapter { mCurrentTiles = tileSpecs; final TileGroup group = new TileGroup("com.android.settings", mContext); String possible = mContext.getString(R.string.quick_settings_tiles_default) - + ",user,hotspot,inversion"; + + ",user,hotspot,inversion,saver"; String[] possibleTiles = possible.split(","); for (int i = 0; i < possibleTiles.length; i++) { final String spec = possibleTiles[i]; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java new file mode 100644 index 000000000000..1aeb0fe6c9dd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.qs.tiles; + +import com.android.internal.logging.MetricsProto.MetricsEvent; +import com.android.systemui.R; +import com.android.systemui.qs.QSTile; +import com.android.systemui.statusbar.policy.DataSaverController; + +public class DataSaverTile extends QSTile<QSTile.BooleanState> implements + DataSaverController.Listener{ + + private final DataSaverController mDataSaverController; + + public DataSaverTile(Host host) { + super(host); + mDataSaverController = host.getNetworkController().getDataSaverController(); + } + + @Override + protected BooleanState newTileState() { + return new BooleanState(); + } + + @Override + public void setListening(boolean listening) { + if (listening) { + mDataSaverController.addListener(this); + } else { + mDataSaverController.remListener(this); + } + } + + @Override + protected void handleClick() { + mState.value = !mDataSaverController.isDataSaverEnabled(); + mDataSaverController.setDataSaverEnabled(mState.value); + refreshState(mState.value); + } + + @Override + protected void handleUpdateState(BooleanState state, Object arg) { + state.value = arg instanceof Boolean ? (Boolean) arg + : mDataSaverController.isDataSaverEnabled(); + state.label = mContext.getString(R.string.data_saver); + state.contentDescription = mContext.getString(state.value ? + R.string.accessibility_data_saver_on : R.string.accessibility_data_saver_off); + state.icon = ResourceIcon.get(state.value ? R.drawable.ic_data_saver + : R.drawable.ic_data_saver_off); + } + + @Override + public int getMetricsCategory() { + return MetricsEvent.QS_DATA_SAVER; + } + + @Override + public void onDataSaverChanged(boolean isDataSaving) { + refreshState(isDataSaving); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java index 881aa6ac688f..5cd540bcb789 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsDebugFlags.java @@ -26,10 +26,6 @@ import com.android.systemui.tuner.TunerService; */ public class RecentsDebugFlags implements TunerService.Tunable { - private static final String KEY_FAST_TOGGLE = "overview_fast_toggle_via_button"; - private static final String KEY_FAST_TOGGLE_INDICATOR = "overview_fast_toggle_indicator"; - private static final String KEY_INITIAL_STATE_PAGING = "overview_initial_state_paging"; - public static class Static { // Enables debug drawing for the transition thumbnail public static final boolean EnableTransitionThumbnailDebugMode = false; @@ -39,18 +35,23 @@ public class RecentsDebugFlags implements TunerService.Tunable { public static final boolean DisableBackgroundCache = false; // Enables the task affiliations public static final boolean EnableAffiliatedTaskGroups = true; - // Enables the simulated task affiliations - public static final boolean EnableSimulatedTaskGroups = false; - // Defines the number of mock task affiliations per group - public static final int TaskAffiliationsGroupCount = 12; + // Enables us to create mock recents tasks - public static final boolean EnableSystemServicesProxy = false; + public static final boolean EnableMockTasks = false; // Defines the number of mock recents packages to create - public static final int SystemServicesProxyMockPackageCount = 3; + public static final int MockTasksPackageCount = 3; // Defines the number of mock recents tasks to create - public static final int SystemServicesProxyMockTaskCount = 100; + public static final int MockTaskCount = 100; + // Enables the simulated task affiliations + public static final boolean EnableMockTaskGroups = false; + // Defines the number of mock task affiliations per group + public static final int MockTaskGroupsTaskCount = 12; } + private static final String KEY_FAST_TOGGLE = "overview_fast_toggle_via_button"; + private static final String KEY_FAST_TOGGLE_INDICATOR = "overview_fast_toggle_indicator"; + private static final String KEY_INITIAL_STATE_PAGING = "overview_initial_state_paging"; + private boolean mFastToggleRecents; private boolean mFastToggleIndicator; private boolean mInitialStatePaging; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index f8cbf656dfa0..5f11bee9249a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -108,6 +108,10 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub implements public void onActivityPinned() { } + @Override + public void onPinnedActivityRestartAttempt() { + } + /** Preloads the next task */ public void run() { RecentsConfiguration config = Recents.getConfiguration(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index 3f52ae8d7ed1..87cfcff1066e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -151,7 +151,7 @@ public class SystemServicesProxy { // Resolve the assist intent mAssistComponent = mAssistUtils.getAssistComponentForUser(UserHandle.myUserId()); - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { + if (RecentsDebugFlags.Static.EnableMockTasks) { // Create a dummy icon mDummyIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); mDummyIcon.eraseColor(0xFF999999); @@ -164,20 +164,20 @@ public class SystemServicesProxy { if (mAm == null) return null; // If we are mocking, then create some recent tasks - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { + if (RecentsDebugFlags.Static.EnableMockTasks) { ArrayList<ActivityManager.RecentTaskInfo> tasks = new ArrayList<ActivityManager.RecentTaskInfo>(); - int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.SystemServicesProxyMockTaskCount); + int count = Math.min(numLatestTasks, RecentsDebugFlags.Static.MockTaskCount); for (int i = 0; i < count; i++) { // Create a dummy component name - int packageIndex = i % RecentsDebugFlags.Static.SystemServicesProxyMockPackageCount; + int packageIndex = i % RecentsDebugFlags.Static.MockTasksPackageCount; ComponentName cn = new ComponentName("com.android.test" + packageIndex, "com.android.test" + i + ".Activity"); String description = "" + i + " - " + Long.toString(Math.abs(new Random().nextLong()), 36); // Create the recent task info ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo(); - rti.id = rti.persistentId = i; + rti.id = rti.persistentId = rti.affiliatedTaskId = i; rti.baseIntent = new Intent(); rti.baseIntent.setComponent(cn); rti.description = description; @@ -418,7 +418,7 @@ public class SystemServicesProxy { if (mAm == null) return null; // If we are mocking, then just return a dummy thumbnail - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { + if (RecentsDebugFlags.Static.EnableMockTasks) { Bitmap thumbnail = Bitmap.createBitmap(mDummyThumbnailWidth, mDummyThumbnailHeight, Bitmap.Config.ARGB_8888); thumbnail.eraseColor(0xff333333); @@ -484,7 +484,7 @@ public class SystemServicesProxy { /** Moves a task to the front with the specified activity options. */ public void moveTaskToFront(int taskId, ActivityOptions opts) { if (mAm == null) return; - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return; + if (RecentsDebugFlags.Static.EnableMockTasks) return; if (opts != null) { mAm.moveTaskToFront(taskId, ActivityManager.MOVE_TASK_WITH_HOME, @@ -497,7 +497,7 @@ public class SystemServicesProxy { /** Removes the task */ public void removeTask(final int taskId) { if (mAm == null) return; - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return; + if (RecentsDebugFlags.Static.EnableMockTasks) return; // Remove the task. BackgroundThread.getHandler().post(new Runnable() { @@ -528,7 +528,7 @@ public class SystemServicesProxy { */ public ActivityInfo getActivityInfo(ComponentName cn, int userId) { if (mIpm == null) return null; - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return new ActivityInfo(); + if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo(); try { return mIpm.getActivityInfo(cn, PackageManager.GET_META_DATA, userId); @@ -545,7 +545,7 @@ public class SystemServicesProxy { */ public ActivityInfo getActivityInfo(ComponentName cn) { if (mPm == null) return null; - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return new ActivityInfo(); + if (RecentsDebugFlags.Static.EnableMockTasks) return new ActivityInfo(); try { return mPm.getActivityInfo(cn, PackageManager.GET_META_DATA); @@ -562,7 +562,7 @@ public class SystemServicesProxy { if (mPm == null) return null; // If we are mocking, then return a mock label - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { + if (RecentsDebugFlags.Static.EnableMockTasks) { return "Recent Task: " + userId; } @@ -576,7 +576,7 @@ public class SystemServicesProxy { if (mPm == null) return null; // If we are mocking, then return a mock label - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { + if (RecentsDebugFlags.Static.EnableMockTasks) { return "Recent Task App: " + userId; } @@ -588,6 +588,11 @@ public class SystemServicesProxy { * description joins the app and activity labels. */ public String getBadgedContentDescription(ActivityInfo info, int userId, Resources res) { + // If we are mocking, then return a mock label + if (RecentsDebugFlags.Static.EnableMockTasks) { + return "Recent Task Content Description: " + userId; + } + String activityLabel = info.loadLabel(mPm).toString(); String applicationLabel = info.applicationInfo.loadLabel(mPm).toString(); String badgedApplicationLabel = getBadgedLabel(applicationLabel, userId); @@ -604,7 +609,7 @@ public class SystemServicesProxy { if (mPm == null) return null; // If we are mocking, then return a mock label - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { + if (RecentsDebugFlags.Static.EnableMockTasks) { return new ColorDrawable(0xFF666666); } @@ -620,7 +625,7 @@ public class SystemServicesProxy { if (mPm == null) return null; // If we are mocking, then return a mock label - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { + if (RecentsDebugFlags.Static.EnableMockTasks) { return new ColorDrawable(0xFF666666); } @@ -635,7 +640,7 @@ public class SystemServicesProxy { int userId, Resources res) { // If we are mocking, then return a mock label - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) { + if (RecentsDebugFlags.Static.EnableMockTasks) { return new ColorDrawable(0xFF666666); } @@ -673,7 +678,7 @@ public class SystemServicesProxy { /** Returns the package name of the home activity. */ public String getHomeActivityPackageName() { if (mPm == null) return null; - if (RecentsDebugFlags.Static.EnableSystemServicesProxy) return null; + if (RecentsDebugFlags.Static.EnableMockTasks) return null; ArrayList<ResolveInfo> homeActivities = new ArrayList<>(); ComponentName defaultHomeActivity = mPm.getHomeActivities(homeActivities); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 9cdd703852fe..d15828a96554 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -22,6 +22,7 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; +import android.os.Debug; import android.os.UserHandle; import android.os.UserManager; import android.util.ArraySet; @@ -31,6 +32,7 @@ import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsConfiguration; +import com.android.systemui.recents.RecentsDebugFlags; import com.android.systemui.recents.misc.SystemServicesProxy; import java.util.ArrayList; @@ -130,6 +132,9 @@ public class RecentsTaskLoadPlan { R.string.accessibility_recents_item_will_be_dismissed); long lastStackActiveTime = Prefs.getLong(mContext, Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, 0); + if (RecentsDebugFlags.Static.EnableMockTasks) { + lastStackActiveTime = 0; + } long newLastStackActiveTime = -1; int taskCount = mRawTasks.size(); for (int i = 0; i < taskCount; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index de1daa8ea988..66eeac60d801 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -776,7 +776,7 @@ public class TaskStack { * Temporary: This method will simulate affiliation groups by */ public void createAffiliatedGroupings(Context context) { - if (RecentsDebugFlags.Static.EnableSimulatedTaskGroups) { + if (RecentsDebugFlags.Static.EnableMockTaskGroups) { ArrayMap<Task.TaskKey, Task> taskMap = new ArrayMap<>(); // Sort all tasks by increasing firstActiveTime of the task ArrayList<Task> tasks = mStackTaskList.getTasks(); @@ -792,7 +792,7 @@ public class TaskStack { String prevPackage = ""; int prevAffiliation = -1; Random r = new Random(); - int groupCountDown = RecentsDebugFlags.Static.TaskAffiliationsGroupCount; + int groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount; for (int i = 0; i < taskCount; i++) { Task t = tasks.get(i); String packageName = t.key.getComponent().getPackageName(); @@ -807,7 +807,7 @@ public class TaskStack { addGroup(group); prevAffiliation = affiliation; prevPackage = packageName; - groupCountDown = RecentsDebugFlags.Static.TaskAffiliationsGroupCount; + groupCountDown = RecentsDebugFlags.Static.MockTaskGroupsTaskCount; } group.addTask(t); taskMap.put(t.key, t); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeFrameLayout.java b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeFrameLayout.java new file mode 100644 index 000000000000..9f2b00a50e60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeFrameLayout.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.recents.views; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +/** + * This is an optimized FrameLayout whose layout is completely directed by its parent, and as a + * result, does not propagate <code>requestLayout()</code> up the view hierarchy. Instead, it will + * relayout its children with the last known layout bounds when a layout is requested from a child + * view. + */ +public class FixedSizeFrameLayout extends FrameLayout { + + private final Rect mLayoutBounds = new Rect(); + + public FixedSizeFrameLayout(Context context) { + super(context); + } + + public FixedSizeFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public FixedSizeFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public FixedSizeFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected final void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + measureContents(MeasureSpec.getSize(widthMeasureSpec), + MeasureSpec.getSize(heightMeasureSpec)); + } + + @Override + protected final void onLayout(boolean changed, int left, int top, int right, int bottom) { + mLayoutBounds.set(left, top, right, bottom); + layoutContents(mLayoutBounds, changed); + } + + @Override + public final void requestLayout() { + // The base ViewGroup constructor attempts to call requestLayout() before this class's + // members are initialized so we should just propagate in that case + if (mLayoutBounds == null || mLayoutBounds.isEmpty()) { + super.requestLayout(); + } else { + // If we are already laid out, then just reuse the same bounds to layout the children + // (but not itself) + // TODO: Investigate whether we should coalesce these to the next frame if needed + measureContents(getMeasuredWidth(), getMeasuredHeight()); + layoutContents(mLayoutBounds, false); + } + } + + /** + * Measures the contents of this fixed layout. + */ + protected void measureContents(int width, int height) { + super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); + } + + /** + * Lays out the contents of this fixed layout. + */ + protected void layoutContents(Rect bounds, boolean changed) { + super.onLayout(changed, bounds.left, bounds.top, bounds.right, bounds.bottom); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java index 3f5d0a846299..f5ab01f5bf5b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/FixedSizeImageView.java @@ -23,13 +23,13 @@ import android.util.AttributeSet; import android.widget.ImageView; /** - * This is an optimized ImageView that does not trigger a requestLayout() or invalidate() when - * setting the image to Null. + * This is an optimized ImageView that does not trigger a <code>requestLayout()</code> or + * <code>invalidate()</code> when setting the image to <code>null</code>. */ public class FixedSizeImageView extends ImageView { - boolean mAllowRelayout = true; - boolean mAllowInvalidate = true; + private boolean mAllowRelayout = true; + private boolean mAllowInvalidate = true; public FixedSizeImageView(Context context) { this(context, null); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index fe9c68eaf239..ccc858100d48 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -1375,11 +1375,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Report that this tasks's data is no longer being used Recents.getTaskLoader().unloadTaskData(task); - // Detach the view from the hierarchy - detachViewFromParent(tv); - // Update the task views list after removing the task view - updateTaskViewsList(); - // Reset the view properties and view state tv.resetViewProperties(); tv.setFocusedState(false, false /* requestViewFocus */); @@ -1387,19 +1382,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (mScreenPinningEnabled) { tv.hideActionButton(false /* fadeOut */, 0 /* duration */, false /* scaleDown */, null); } + + // Detach the view from the hierarchy + detachViewFromParent(tv); + // Update the task views list after removing the task view + updateTaskViewsList(); } @Override public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) { - // Rebind the task and request that this task's data be filled into the TaskView - tv.onTaskBound(task); - - // Load the task data - Recents.getTaskLoader().loadTaskData(task); - - // If the doze trigger has already fired, then update the state for this task view - tv.setNoUserInteractionState(); - // Find the index where this task should be placed in the stack int taskIndex = mStack.indexOfStackTask(task); int insertIndex = findTaskViewInsertIndex(task, taskIndex); @@ -1413,6 +1404,15 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Update the task views list after adding the new task view updateTaskViewsList(); + // Rebind the task and request that this task's data be filled into the TaskView + tv.onTaskBound(task); + + // Load the task data + Recents.getTaskLoader().loadTaskData(task); + + // If the doze trigger has already fired, then update the state for this task view + tv.setNoUserInteractionState(); + // Set the new state for this view, including the callbacks and view clipping tv.setCallbacks(this); tv.setTouchEnabled(true); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 32bebb36d17f..5a4064a686bf 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -17,7 +17,6 @@ package com.android.systemui.recents.views; import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; @@ -40,7 +39,6 @@ import android.view.ViewOutlineProvider; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; -import android.widget.FrameLayout; import com.android.systemui.R; import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsActivity; @@ -62,8 +60,13 @@ import java.util.ArrayList; import static android.app.ActivityManager.StackId.INVALID_STACK_ID; -/* A task view */ -public class TaskView extends FrameLayout implements Task.TaskCallbacks, +/** + * A {@link TaskView} represents a fixed view of a task. Because the TaskView's layout is directed + * solely by the {@link TaskStackView}, we make it a fixed size layout which allows relayouts down + * the view hierarchy, but not upwards from any of its children (the TaskView will relayout itself + * with the previous bounds if any child requests layout). + */ +public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks, TaskStackAnimationHelper.Callbacks, View.OnClickListener, View.OnLongClickListener { /** The TaskView callbacks */ @@ -219,33 +222,20 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, return super.onInterceptTouchEvent(ev); } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = MeasureSpec.getSize(widthMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); + @Override + protected void measureContents(int width, int height) { int widthWithoutPadding = width - mPaddingLeft - mPaddingRight; int heightWithoutPadding = height - mPaddingTop - mPaddingBottom; - int taskBarHeight = getResources().getDimensionPixelSize(R.dimen.recents_task_bar_height); // Measure the content mContent.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY)); - // Measure the bar view, and action button - mHeaderView.measure(MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(taskBarHeight, MeasureSpec.EXACTLY)); - mActionButtonView.measure( - MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.AT_MOST), - MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.AT_MOST)); - // Measure the thumbnail to be square - mThumbnailView.measure( - MeasureSpec.makeMeasureSpec(widthWithoutPadding, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(heightWithoutPadding, MeasureSpec.EXACTLY)); + // Optimization: Prevent overdraw of the thumbnail under the header view mThumbnailView.updateClipToTaskBar(mHeaderView); setMeasuredDimension(width, height); - invalidateOutline(); } void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 827ee4088d57..cb108daab3ac 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -245,11 +245,7 @@ public class TaskViewHeader extends FrameLayout * to match the frame changes. */ public void onTaskViewSizeChanged(int width, int height) { - // Return early if the bounds have not changed - if (mTaskViewRect.width() == width && mTaskViewRect.height() == height) { - return; - } - + // TODO: Optimize this path mTaskViewRect.set(0, 0, width, height); boolean updateMoveTaskButton = mMoveTaskButton.getVisibility() != View.GONE; boolean isFreeformTask = (mTask != null) && mTask.isFreeformTask(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java index 39d06049edaf..de96d9da8e85 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java @@ -190,7 +190,6 @@ public class TaskViewThumbnail extends View { if (!mInvisible) { updateThumbnailPaintFilter(); } - invalidate(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index a8e09f805641..e20936b7dec4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -105,6 +105,10 @@ public class CarStatusBar extends PhoneStatusBar { } @Override + public void onPinnedActivityRestartAttempt() { + } + + @Override public void onTaskStackChanged() { mHandler.removeCallbacks(this); mHandler.post(this); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java index d0c14f1fd52b..ed6d94004ad8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarApps.java @@ -1108,6 +1108,10 @@ class NavigationBarApps extends LinearLayout @Override public void onActivityPinned() { } + + @Override + public void onPinnedActivityRestartAttempt() { + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 7e278560ba55..9c2159be1b98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -38,6 +38,7 @@ import com.android.systemui.qs.tiles.BluetoothTile; import com.android.systemui.qs.tiles.CastTile; import com.android.systemui.qs.tiles.CellularTile; import com.android.systemui.qs.tiles.ColorInversionTile; +import com.android.systemui.qs.tiles.DataSaverTile; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.FlashlightTile; import com.android.systemui.qs.tiles.HotspotTile; @@ -346,6 +347,7 @@ public final class QSTileHost implements QSTile.Host, Tunable { else if (tileSpec.equals("hotspot")) return new HotspotTile(this); else if (tileSpec.equals("user")) return new UserTile(this); else if (tileSpec.equals("battery")) return new BatteryTile(this); + else if (tileSpec.equals("saver")) return new DataSaverTile(this); else if (tileSpec.equals(ColorMatrixTile.COLOR_MATRIX_SPEC)) return new ColorMatrixTile(this); // Intent tiles. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java new file mode 100644 index 000000000000..186e8b51d58c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import android.content.Context; +import android.net.INetworkPolicyListener; +import android.net.NetworkPolicyManager; +import android.os.Handler; +import android.os.RemoteException; + +import java.util.ArrayList; + +public class DataSaverController { + + private final Handler mHandler = new Handler(); + private final ArrayList<Listener> mListeners = new ArrayList<>(); + private final NetworkPolicyManager mPolicyManager; + + public DataSaverController(Context context) { + mPolicyManager = NetworkPolicyManager.from(context); + } + + private void handleRestrictBackgroundChanged(boolean isDataSaving) { + final int N = mListeners.size(); + for (int i = 0; i < N; i++) { + mListeners.get(i).onDataSaverChanged(isDataSaving); + } + } + + public void addListener(Listener listener) { + mListeners.add(listener); + if (mListeners.size() == 1) { + mPolicyManager.registerListener(mPolicyListener); + } + listener.onDataSaverChanged(isDataSaverEnabled()); + } + + public void remListener(Listener listener) { + mListeners.remove(listener); + if (mListeners.size() == 0) { + mPolicyManager.unregisterListener(mPolicyListener); + } + } + + public boolean isDataSaverEnabled() { + return mPolicyManager.getRestrictBackground(); + } + + public void setDataSaverEnabled(boolean enabled) { + mPolicyManager.setRestrictBackground(enabled); + } + + private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() { + @Override + public void onUidRulesChanged(int i, int i1) throws RemoteException { + } + + @Override + public void onMeteredIfacesChanged(String[] strings) throws RemoteException { + } + + @Override + public void onRestrictBackgroundChanged(final boolean isDataSaving) throws RemoteException { + mHandler.post(new Runnable() { + @Override + public void run() { + handleRestrictBackgroundChanged(isDataSaving); + } + }); + } + }; + + public interface Listener { + void onDataSaverChanged(boolean isDataSaving); + } + +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java index b2bcde3db937..755a5b3f511d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java @@ -33,6 +33,7 @@ public interface NetworkController { void onUserSwitched(int newUserId); AccessPointController getAccessPointController(); DataUsageController getMobileDataController(); + DataSaverController getDataSaverController(); public interface SignalCallback { void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 3385c820a64f..dbb0295e7029 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -78,6 +78,7 @@ public class NetworkControllerImpl extends BroadcastReceiver private final SubscriptionManager mSubscriptionManager; private final boolean mHasMobileDataFeature; private final SubscriptionDefaults mSubDefaults; + private final DataSaverController mDataSaverController; private Config mConfig; // Subcontrollers. @@ -156,6 +157,7 @@ public class NetworkControllerImpl extends BroadcastReceiver mConfig = config; mReceiverHandler = new Handler(bgLooper); mCallbackHandler = callbackHandler; + mDataSaverController = new DataSaverController(context); mSubscriptionManager = subManager; mSubDefaults = defaultsHandler; @@ -189,6 +191,10 @@ public class NetworkControllerImpl extends BroadcastReceiver updateAirplaneMode(true /* force callback */); } + public DataSaverController getDataSaverController() { + return mDataSaverController; + } + private void registerListeners() { for (MobileSignalController mobileSignalController : mMobileSignalControllers.values()) { mobileSignalController.registerListener(); diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java index bed1e9e39cce..b237400db484 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java @@ -29,7 +29,6 @@ import android.content.res.Resources; import android.graphics.Rect; import android.os.Handler; import android.os.RemoteException; -import android.os.UserHandle; import android.util.Log; import java.util.ArrayList; @@ -38,7 +37,6 @@ import java.util.List; import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; -import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; /** @@ -314,6 +312,10 @@ public class PipManager { // Post the message back to the UI thread. mHandler.post(mOnActivityPinnedRunnable); } + + @Override + public void onPinnedActivityRestartAttempt() { + } } /** diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 88e55ddb4fda..697777c718a3 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -339,5 +339,8 @@ message MetricsEvent { // Logged when the user calls an emergency contact. ACTION_CALL_EMERGENCY_CONTACT = 283; + + // QS Tile for Data Saver. + QS_DATA_SAVER = 284; } } diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java index a4876b92fadf..a71ba6364ac8 100644 --- a/rs/java/android/renderscript/Allocation.java +++ b/rs/java/android/renderscript/Allocation.java @@ -16,14 +16,16 @@ package android.renderscript; +import java.nio.ByteBuffer; import java.util.HashMap; + import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.view.Surface; -import android.util.Log; import android.graphics.Canvas; import android.os.Trace; +import android.util.Log; +import android.view.Surface; /** * <p> This class provides the primary method through which data is passed to @@ -78,6 +80,8 @@ public class Allocation extends BaseObj { OnBufferAvailableListener mBufferNotifier; private Surface mGetSurfaceSurface = null; + private ByteBuffer mByteBuffer = null; + private long mByteBufferStride = -1; private Element.DataType validateObjectIsPrimitiveArray(Object d, boolean checkType) { final Class c = d.getClass(); @@ -2050,6 +2054,59 @@ public class Allocation extends BaseObj { } /** + * @hide + * Gets or creates a ByteBuffer that contains the raw data of the current Allocation. + * If the Allocation is created with USAGE_IO_INPUT, the returned ByteBuffer + * would contain the up-to-date data as READ ONLY. + * For a 2D or 3D Allocation, the raw data maybe padded so that each row of + * the Allocation has certain alignment. The size of each row including padding, + * called stride, can be queried using the {@link #getStride()} method. + * + * Note: Operating on the ByteBuffer of a destroyed Allocation will triger errors. + * + * @return ByteBuffer The ByteBuffer associated with raw data pointer of the Allocation. + */ + public ByteBuffer getByteBuffer() { + // Create a new ByteBuffer if it is not initialized or using IO_INPUT. + if (mType.hasFaces()) { + throw new RSInvalidStateException("Cubemap is not supported for getByteBuffer()."); + } + if (mType.getYuv() == android.graphics.ImageFormat.NV21 || + mType.getYuv() == android.graphics.ImageFormat.YV12 || + mType.getYuv() == android.graphics.ImageFormat.YUV_420_888 ) { + throw new RSInvalidStateException("YUV format is not supported for getByteBuffer()."); + } + if (mByteBuffer == null || (mUsage & USAGE_IO_INPUT) != 0) { + int xBytesSize = mType.getX() * mType.getElement().getBytesSize(); + long[] stride = new long[1]; + mByteBuffer = mRS.nAllocationGetByteBuffer(getID(mRS), stride, xBytesSize, mType.getY(), mType.getZ()); + mByteBufferStride = stride[0]; + } + if ((mUsage & USAGE_IO_INPUT) != 0) { + return mByteBuffer.asReadOnlyBuffer(); + } + return mByteBuffer; + } + + /** + * @hide + * Gets the stride of the Allocation. + * For a 2D or 3D Allocation, the raw data maybe padded so that each row of + * the Allocation has certain alignment. The size of each row including such + * padding is called stride. + * + * @return the stride. For 1D Allocation, the stride will be the number of + * bytes of this Allocation. For 2D and 3D Allocations, the stride + * will be the stride in X dimension measuring in bytes. + */ + public long getStride() { + if (mByteBufferStride == -1) { + getByteBuffer(); + } + return mByteBufferStride; + } + + /** * Returns the handle to a raw buffer that is being managed by the screen * compositor. This operation is only valid for Allocations with {@link * #USAGE_IO_INPUT}. diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java index 3668ddd62303..4788223b4357 100644 --- a/rs/java/android/renderscript/RenderScript.java +++ b/rs/java/android/renderscript/RenderScript.java @@ -18,17 +18,18 @@ package android.renderscript; import java.io.File; import java.lang.reflect.Method; +import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.concurrent.locks.ReentrantReadWriteLock; import android.content.Context; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.SurfaceTexture; -import android.util.Log; -import android.view.Surface; import android.os.SystemProperties; import android.os.Trace; -import java.util.ArrayList; +import android.util.Log; +import android.view.Surface; // TODO: Clean up the whitespace that separates methods in this class. @@ -489,6 +490,13 @@ public class RenderScript { validate(); rsnAllocationSyncAll(mContext, alloc, src); } + + native ByteBuffer rsnAllocationGetByteBuffer(long con, long alloc, long[] stride, int xBytesSize, int dimY, int dimZ); + synchronized ByteBuffer nAllocationGetByteBuffer(long alloc, long[] stride, int xBytesSize, int dimY, int dimZ) { + validate(); + return rsnAllocationGetByteBuffer(mContext, alloc, stride, xBytesSize, dimY, dimZ); + } + native Surface rsnAllocationGetSurface(long con, long alloc); synchronized Surface nAllocationGetSurface(long alloc) { validate(); diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java index 84f980dc24cd..2b0678046e32 100644 --- a/rs/java/android/renderscript/Script.java +++ b/rs/java/android/renderscript/Script.java @@ -315,7 +315,6 @@ public class Script extends BaseObj { /** * Only intended for use by generated reflected code. (General reduction) * - * @hide */ protected void reduce(int slot, Allocation[] ains, Allocation aout, LaunchOptions sc) { mRS.validate(); diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp index 39540709b16e..398d89b20b1e 100644 --- a/rs/jni/android_renderscript_RenderScript.cpp +++ b/rs/jni/android_renderscript_RenderScript.cpp @@ -2752,7 +2752,43 @@ nSystemGetPointerSize(JNIEnv *_env, jobject _this) { return (jint)sizeof(void*); } +static jobject +nAllocationGetByteBuffer(JNIEnv *_env, jobject _this, jlong con, jlong alloc, + jlongArray strideArr, jint xBytesSize, + jint dimY, jint dimZ) { + if (kLogApi) { + ALOGD("nAllocationGetByteBuffer, con(%p), alloc(%p)", (RsContext)con, (RsAllocation)alloc); + } + + jlong *jStridePtr = _env->GetLongArrayElements(strideArr, nullptr); + if (jStridePtr == nullptr) { + ALOGE("Failed to get Java array elements: strideArr"); + return 0; + } + size_t strideIn = xBytesSize; + void* ptr = nullptr; + if (alloc != 0) { + ptr = rsAllocationGetPointer((RsContext)con, (RsAllocation)alloc, 0, + RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X, 0, 0, + &strideIn, sizeof(size_t)); + } + + jobject byteBuffer = nullptr; + if (ptr != nullptr) { + size_t bufferSize = strideIn; + jStridePtr[0] = strideIn; + if (dimY > 0) { + bufferSize *= dimY; + } + if (dimZ > 0) { + bufferSize *= dimZ; + } + byteBuffer = _env->NewDirectByteBuffer(ptr, (jlong) bufferSize); + } + _env->ReleaseLongArrayElements(strideArr, jStridePtr, 0); + return byteBuffer; +} // --------------------------------------------------------------------------- @@ -2909,6 +2945,7 @@ static const JNINativeMethod methods[] = { {"rsnMeshGetIndices", "(JJ[J[II)V", (void*)nMeshGetIndices }, {"rsnSystemGetPointerSize", "()I", (void*)nSystemGetPointerSize }, +{"rsnAllocationGetByteBuffer", "(JJ[JIII)Ljava/nio/ByteBuffer;", (void*)nAllocationGetByteBuffer }, }; static int registerFuncs(JNIEnv *_env) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 1434e5e1b759..b8327c14a984 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -203,7 +203,6 @@ import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseBooleanArray; import android.util.TimeUtils; import android.util.Xml; import android.view.Display; @@ -1473,6 +1472,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final int LOG_STACK_STATE = 62; static final int VR_MODE_CHANGE_MSG = 63; static final int NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG = 64; + static final int NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG = 65; static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -2056,6 +2056,20 @@ public final class ActivityManagerService extends ActivityManagerNative } break; } + case NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG: { + synchronized (ActivityManagerService.this) { + for (int i = mTaskStackListeners.beginBroadcast() - 1; i >= 0; i--) { + try { + // Make a one-way callback to the listener + mTaskStackListeners.getBroadcastItem(i).onPinnedActivityRestartAttempt(); + } catch (RemoteException e){ + // Handled by the RemoteCallbackList + } + } + mTaskStackListeners.finishBroadcast(); + } + break; + } case NOTIFY_CLEARTEXT_NETWORK_MSG: { final int uid = msg.arg1; final byte[] firstPacket = (byte[]) msg.obj; @@ -11284,6 +11298,16 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG).sendToTarget(); } + /** + * Notifies all listeners when an attempt was made to start an an activity that is already + * running in the pinned stack and the activity was not actually started, but the task is + * either brought to the front or a new Intent is delivered to it. + */ + void notifyPinnedActivityRestartAttemptLocked() { + mHandler.removeMessages(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG); + mHandler.obtainMessage(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG).sendToTarget(); + } + @Override public void notifyCleartextNetwork(int uid, byte[] firstPacket) { mHandler.obtainMessage(NOTIFY_CLEARTEXT_NETWORK_MSG, uid, 0, firstPacket).sendToTarget(); diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 23dc0f6746ed..7b7359f5ee1e 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -504,18 +504,45 @@ class ActivityStarter { err = startActivityUnchecked( r, sourceRecord, voiceSession, voiceInteractor, startFlags, true, options, inTask); + postStartActivityUncheckedProcessing(r, err, stack.mStackId); + return err; + } - if (err < 0) { - // If someone asked to have the keyguard dismissed on the next - // activity start, but we are not actually doing an activity - // switch... just dismiss the keyguard now, because we - // probably want to see whatever is behind it. + void postStartActivityUncheckedProcessing( + ActivityRecord r, int result, int prevFocusedStackId) { + + if (result < START_SUCCESS) { + // If someone asked to have the keyguard dismissed on the next activity start, + // but we are not actually doing an activity switch... just dismiss the keyguard now, + // because we probably want to see whatever is behind it. mSupervisor.notifyActivityDrawnForKeyguard(); - } else { - launchRecentsAppIfNeeded(stack); + return; } - return err; + int startedActivityStackId = INVALID_STACK_ID; + if (r.task != null && r.task.stack != null) { + startedActivityStackId = r.task.stack.mStackId; + } else if (mTargetStack != null) { + startedActivityStackId = mTargetStack.mStackId; + } + + if (startedActivityStackId == DOCKED_STACK_ID && prevFocusedStackId == HOME_STACK_ID) { + // We launch an activity while being in home stack, which means either launcher or + // recents into docked stack. We don't want the launched activity to be alone in a + // docked stack, so we want to immediately launch recents too. + if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch."); + mWindowManager.showRecentApps(); + return; + } + + if (startedActivityStackId == PINNED_STACK_ID + && (result == START_TASK_TO_FRONT || result == START_DELIVERED_TO_TOP)) { + // The activity was already running in the pinned stack so it wasn't started, but either + // brought to the front or the new intent was delivered to it since it was already in + // front. Notify anyone interested in this piece of information. + mService.notifyPinnedActivityRestartAttemptLocked(); + return; + } } void startHomeActivityLocked(Intent intent, ActivityInfo aInfo, String reason) { @@ -985,17 +1012,6 @@ class ActivityStarter { return START_SUCCESS; } - private void launchRecentsAppIfNeeded(ActivityStack topStack) { - if (topStack.mStackId == HOME_STACK_ID && mTargetStack != null - && mTargetStack.mStackId == DOCKED_STACK_ID) { - // We launch an activity while being in home stack, which means either launcher or - // recents into docked stack. We don't want the launched activity to be alone in a - // docked stack, so we want to immediately launch recents too. - if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch."); - mWindowManager.showRecentApps(); - } - } - private void setInitialState(ActivityRecord r, ActivityOptions options, TaskRecord inTask, boolean doResume, int startFlags, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor) { @@ -1557,11 +1573,13 @@ class ActivityStarter { final void doPendingActivityLaunchesLocked(boolean doResume) { while (!mPendingActivityLaunches.isEmpty()) { - PendingActivityLaunch pal = mPendingActivityLaunches.remove(0); - + final PendingActivityLaunch pal = mPendingActivityLaunches.remove(0); + final boolean resume = doResume && mPendingActivityLaunches.isEmpty(); try { - startActivityUnchecked(pal.r, pal.sourceRecord, null, null, pal.startFlags, - doResume && mPendingActivityLaunches.isEmpty(), null, null); + final int result = startActivityUnchecked( + pal.r, pal.sourceRecord, null, null, pal.startFlags, resume, null, null); + postStartActivityUncheckedProcessing( + pal.r, result, mSupervisor.mFocusedStack.mStackId); } catch (Exception e) { Slog.e(TAG, "Exception during pending activity launch pal=" + pal, e); pal.sendErrorResult(e.getMessage()); diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 3193ff8a4977..3ca99d400820 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -435,7 +435,11 @@ public final class TvInputManagerService extends SystemService { for (SessionState state : userState.sessionStateMap.values()) { if (state.session != null) { try { - state.session.release(); + if (state.isRecordingSession) { + state.session.disconnect(); + } else { + state.session.release(); + } } catch (RemoteException e) { Slog.e(TAG, "error in release", e); } @@ -604,7 +608,11 @@ public final class TvInputManagerService extends SystemService { // Create a session. When failed, send a null token immediately. try { - service.createSession(channels[1], callback, sessionState.info.getId()); + if (sessionState.isRecordingSession) { + service.createRecordingSession(callback, sessionState.info.getId()); + } else { + service.createSession(channels[1], callback, sessionState.info.getId()); + } } catch (RemoteException e) { Slog.e(TAG, "error in createSession", e); removeSessionStateLocked(sessionToken, userId); @@ -632,7 +640,11 @@ public final class TvInputManagerService extends SystemService { if (sessionToken == userState.mainSessionToken) { setMainLocked(sessionToken, false, callingUid, userId); } - sessionState.session.release(); + if (sessionState.isRecordingSession) { + sessionState.session.disconnect(); + } else { + sessionState.session.release(); + } } } catch (RemoteException | SessionNotFoundException e) { Slog.e(TAG, "error in releaseSession", e); @@ -766,6 +778,21 @@ public final class TvInputManagerService extends SystemService { } } + private void notifyTvInputInfoChanged(UserState userState, String inputId, + TvInputInfo inputInfo) { + if (DEBUG) { + Slog.d(TAG, "notifyTvInputInfoChanged(inputId=" + inputId + ", inputInfo=" + inputInfo + + ")"); + } + for (ITvInputManagerCallback callback : userState.callbackSet) { + try { + callback.onTvInputInfoChanged(inputId, inputInfo); + } catch (RemoteException e) { + Slog.e(TAG, "failed to report changed input info to callback", e); + } + } + } + private void setStateLocked(String inputId, int state, int userId) { UserState userState = getOrCreateUserStateLocked(userId); TvInputState inputState = userState.inputMap.get(inputId); @@ -1005,7 +1032,7 @@ public final class TvInputManagerService extends SystemService { @Override public void createSession(final ITvInputClient client, final String inputId, - int seq, int userId) { + boolean isRecordingSession, int seq, int userId) { final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, userId, "createSession"); @@ -1033,8 +1060,8 @@ public final class TvInputManagerService extends SystemService { // Create a new session token and a session state. IBinder sessionToken = new Binder(); - SessionState sessionState = new SessionState(sessionToken, info, client, - seq, callingUid, resolvedUserId); + SessionState sessionState = new SessionState(sessionToken, info, + isRecordingSession, client, seq, callingUid, resolvedUserId); // Add them to the global session state map of the current user. userState.sessionStateMap.put(sessionToken, sessionState); @@ -1375,6 +1402,26 @@ public final class TvInputManagerService extends SystemService { } @Override + public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "timeShiftPlay"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPlay( + recordedProgramUri); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in timeShiftPlay", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void timeShiftPause(IBinder sessionToken, int userId) { final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, @@ -1383,8 +1430,7 @@ public final class TvInputManagerService extends SystemService { try { synchronized (mLock) { try { - getSessionLocked(sessionToken, callingUid, resolvedUserId) - .timeShiftPause(); + getSessionLocked(sessionToken, callingUid, resolvedUserId).timeShiftPause(); } catch (RemoteException | SessionNotFoundException e) { Slog.e(TAG, "error in timeShiftPause", e); } @@ -1477,6 +1523,64 @@ public final class TvInputManagerService extends SystemService { } @Override + public void connect(IBinder sessionToken, final Uri channelUri, Bundle params, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "connect"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).connect( + channelUri, params); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in connect", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void startRecording(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "startRecording"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording(); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in startRecording", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void stopRecording(IBinder sessionToken, int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "stopRecording"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId).stopRecording(); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in stopRecording", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public List<TvInputHardwareInfo> getHardwareList() throws RemoteException { if (mContext.checkCallingPermission(android.Manifest.permission.TV_INPUT_HARDWARE) != PackageManager.PERMISSION_GRANTED) { @@ -1912,6 +2016,7 @@ public final class TvInputManagerService extends SystemService { private final class SessionState implements IBinder.DeathRecipient { private final TvInputInfo info; + private final boolean isRecordingSession; private final ITvInputClient client; private final int seq; private final int callingUid; @@ -1922,10 +2027,11 @@ public final class TvInputManagerService extends SystemService { // Not null if this session represents an external device connected to a hardware TV input. private IBinder hardwareSessionToken; - private SessionState(IBinder sessionToken, TvInputInfo info, ITvInputClient client, - int seq, int callingUid, int userId) { + private SessionState(IBinder sessionToken, TvInputInfo info, boolean isRecordingSession, + ITvInputClient client, int seq, int callingUid, int userId) { this.sessionToken = sessionToken; this.info = info; + this.isRecordingSession = isRecordingSession; this.client = client; this.seq = seq; this.callingUid = callingUid; @@ -2126,6 +2232,18 @@ public final class TvInputManagerService extends SystemService { } } } + + @Override + public void setTvInputInfo(String inputId, TvInputInfo inputInfo) { + ensureValidInput(inputInfo); + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "setTvInputInfo(" + inputInfo + ")"); + } + UserState userState = getOrCreateUserStateLocked(mUserId); + notifyTvInputInfoChanged(userState, inputId, inputInfo); + } + } } private final class SessionCallback extends ITvInputSessionCallback.Stub { @@ -2393,6 +2511,78 @@ public final class TvInputManagerService extends SystemService { } } } + + // For the recording session only + @Override + public void onConnected() { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onConnected()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onConnected(mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onConnected", e); + } + } + } + + // For the recording session only + @Override + public void onRecordingStarted() { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onRecordingStarted()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onRecordingStarted(mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onRecordingStarted", e); + } + } + } + + // For the recording session only + @Override + public void onRecordingStopped(Uri recordedProgramUri) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onRecordingStopped()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onRecordingStopped(recordedProgramUri, mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onRecordingStopped", e); + } + } + } + + // For the recording session only + @Override + public void onError(int error) { + synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "onError()"); + } + if (mSessionState.session == null || mSessionState.client == null) { + return; + } + try { + mSessionState.client.onError(error, mSessionState.seq); + } catch (RemoteException e) { + Slog.e(TAG, "error in onError", e); + } + } + } } private static final class WatchLogHandler extends Handler { diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java index 90d2aa0223a0..74f11719d237 100644 --- a/telephony/java/android/telephony/CellIdentityGsm.java +++ b/telephony/java/android/telephony/CellIdentityGsm.java @@ -38,6 +38,10 @@ public final class CellIdentityGsm implements Parcelable { private final int mLac; // 16-bit GSM Cell Identity described in TS 27.007, 0..65535 private final int mCid; + // 16-bit GSM Absolute RF Channel Number + private final int mArfcn; + // 6-bit Base Station Identity Code + private final int mBsic; /** * @hide @@ -47,6 +51,8 @@ public final class CellIdentityGsm implements Parcelable { mMnc = Integer.MAX_VALUE; mLac = Integer.MAX_VALUE; mCid = Integer.MAX_VALUE; + mArfcn = Integer.MAX_VALUE; + mBsic = Integer.MAX_VALUE; } /** * public constructor @@ -58,10 +64,27 @@ public final class CellIdentityGsm implements Parcelable { * @hide */ public CellIdentityGsm (int mcc, int mnc, int lac, int cid) { + this(mcc, mnc, lac, cid, Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + /** + * public constructor + * @param mcc 3-digit Mobile Country Code, 0..999 + * @param mnc 2 or 3-digit Mobile Network Code, 0..999 + * @param lac 16-bit Location Area Code, 0..65535 + * @param cid 16-bit GSM Cell Identity or 28-bit UMTS Cell Identity + * @param arfcn 16-bit GSM Absolute RF Channel Number + * @param bsic 6-bit Base Station Identity Code + * + * @hide + */ + public CellIdentityGsm (int mcc, int mnc, int lac, int cid, int arfcn, int bsic) { mMcc = mcc; mMnc = mnc; mLac = lac; mCid = cid; + mArfcn = arfcn; + mBsic = bsic; } private CellIdentityGsm(CellIdentityGsm cid) { @@ -69,6 +92,8 @@ public final class CellIdentityGsm implements Parcelable { mMnc = cid.mMnc; mLac = cid.mLac; mCid = cid.mCid; + mArfcn = cid.mArfcn; + mBsic = cid.mBsic; } CellIdentityGsm copy() { @@ -106,6 +131,21 @@ public final class CellIdentityGsm implements Parcelable { } /** + * @return 16-bit GSM Absolute RF Channel Number, Integer.MAX_VALUE if unknown + */ + public int getArfcn() { + return mArfcn; + } + + /** + * @return 6-bit Base Station Identity Code, Integer.MAX_VALUE if unknown + */ + public int getBsic() { + return mBsic; + } + + + /** * @return Integer.MAX_VALUE, undefined for GSM */ @Deprecated @@ -132,7 +172,9 @@ public final class CellIdentityGsm implements Parcelable { return mMcc == o.mMcc && mMnc == o.mMnc && mLac == o.mLac && - mCid == o.mCid; + mCid == o.mCid && + mArfcn == o.mArfcn && + mBsic == o.mBsic; } @Override @@ -142,6 +184,8 @@ public final class CellIdentityGsm implements Parcelable { sb.append(" mMnc=").append(mMnc); sb.append(" mLac=").append(mLac); sb.append(" mCid=").append(mCid); + sb.append(" mArfcn=").append(mArfcn); + sb.append(" mBsic=").append("0x").append(Integer.toHexString(mBsic)); sb.append("}"); return sb.toString(); @@ -161,6 +205,8 @@ public final class CellIdentityGsm implements Parcelable { dest.writeInt(mMnc); dest.writeInt(mLac); dest.writeInt(mCid); + dest.writeInt(mArfcn); + dest.writeInt(mBsic); } /** Construct from Parcel, type has already been processed */ @@ -169,6 +215,8 @@ public final class CellIdentityGsm implements Parcelable { mMnc = in.readInt(); mLac = in.readInt(); mCid = in.readInt(); + mArfcn = in.readInt(); + mBsic = in.readInt(); if (DBG) log("CellIdentityGsm(Parcel): " + toString()); } diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java index 1e7ac08b3e43..ce7438354a50 100644 --- a/telephony/java/android/telephony/CellIdentityLte.java +++ b/telephony/java/android/telephony/CellIdentityLte.java @@ -40,6 +40,8 @@ public final class CellIdentityLte implements Parcelable { private final int mPci; // 16-bit tracking area code private final int mTac; + // 18-bit Absolute RF Channel Number + private final int mEarfcn; /** * @hide @@ -50,6 +52,7 @@ public final class CellIdentityLte implements Parcelable { mCi = Integer.MAX_VALUE; mPci = Integer.MAX_VALUE; mTac = Integer.MAX_VALUE; + mEarfcn = Integer.MAX_VALUE; } /** @@ -63,11 +66,27 @@ public final class CellIdentityLte implements Parcelable { * @hide */ public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac) { + this(mcc, mnc, ci, pci, tac, Integer.MAX_VALUE); + } + + /** + * + * @param mcc 3-digit Mobile Country Code, 0..999 + * @param mnc 2 or 3-digit Mobile Network Code, 0..999 + * @param ci 28-bit Cell Identity + * @param pci Physical Cell Id 0..503 + * @param tac 16-bit Tracking Area Code + * @param earfcn 18-bit LTE Absolute RF Channel Number + * + * @hide + */ + public CellIdentityLte (int mcc, int mnc, int ci, int pci, int tac, int earfcn) { mMcc = mcc; mMnc = mnc; mCi = ci; mPci = pci; mTac = tac; + mEarfcn = earfcn; } private CellIdentityLte(CellIdentityLte cid) { @@ -76,6 +95,7 @@ public final class CellIdentityLte implements Parcelable { mCi = cid.mCi; mPci = cid.mPci; mTac = cid.mTac; + mEarfcn = cid.mEarfcn; } CellIdentityLte copy() { @@ -117,6 +137,13 @@ public final class CellIdentityLte implements Parcelable { return mTac; } + /** + * @return 18-bit Absolute RF Channel Number, Integer.MAX_VALUE if unknown + */ + public int getEarfcn() { + return mEarfcn; + } + @Override public int hashCode() { return Objects.hash(mMcc, mMnc, mCi, mPci, mTac); @@ -137,7 +164,8 @@ public final class CellIdentityLte implements Parcelable { mMnc == o.mMnc && mCi == o.mCi && mPci == o.mPci && - mTac == o.mTac; + mTac == o.mTac && + mEarfcn == o.mEarfcn; } @Override @@ -148,6 +176,7 @@ public final class CellIdentityLte implements Parcelable { sb.append(" mCi="); sb.append(mCi); sb.append(" mPci="); sb.append(mPci); sb.append(" mTac="); sb.append(mTac); + sb.append(" mEarfcn="); sb.append(mEarfcn); sb.append("}"); return sb.toString(); @@ -168,6 +197,7 @@ public final class CellIdentityLte implements Parcelable { dest.writeInt(mCi); dest.writeInt(mPci); dest.writeInt(mTac); + dest.writeInt(mEarfcn); } /** Construct from Parcel, type has already been processed */ @@ -177,6 +207,7 @@ public final class CellIdentityLte implements Parcelable { mCi = in.readInt(); mPci = in.readInt(); mTac = in.readInt(); + mEarfcn = in.readInt(); if (DBG) log("CellIdentityLte(Parcel): " + toString()); } diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java index 56ee8c9df138..0d13efd279c9 100644 --- a/telephony/java/android/telephony/CellIdentityWcdma.java +++ b/telephony/java/android/telephony/CellIdentityWcdma.java @@ -40,6 +40,8 @@ public final class CellIdentityWcdma implements Parcelable { private final int mCid; // 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511 private final int mPsc; + // 16-bit UMTS Absolute RF Channel Number + private final int mUarfcn; /** * @hide @@ -50,6 +52,7 @@ public final class CellIdentityWcdma implements Parcelable { mLac = Integer.MAX_VALUE; mCid = Integer.MAX_VALUE; mPsc = Integer.MAX_VALUE; + mUarfcn = Integer.MAX_VALUE; } /** * public constructor @@ -62,11 +65,27 @@ public final class CellIdentityWcdma implements Parcelable { * @hide */ public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc) { + this(mcc, mnc, lac, cid, psc, Integer.MAX_VALUE); + } + + /** + * public constructor + * @param mcc 3-digit Mobile Country Code, 0..999 + * @param mnc 2 or 3-digit Mobile Network Code, 0..999 + * @param lac 16-bit Location Area Code, 0..65535 + * @param cid 28-bit UMTS Cell Identity + * @param psc 9-bit UMTS Primary Scrambling Code + * @param uarfcn 16-bit UMTS Absolute RF Channel Number + * + * @hide + */ + public CellIdentityWcdma (int mcc, int mnc, int lac, int cid, int psc, int uarfcn) { mMcc = mcc; mMnc = mnc; mLac = lac; mCid = cid; mPsc = psc; + mUarfcn = uarfcn; } private CellIdentityWcdma(CellIdentityWcdma cid) { @@ -75,6 +94,7 @@ public final class CellIdentityWcdma implements Parcelable { mLac = cid.mLac; mCid = cid.mCid; mPsc = cid.mPsc; + mUarfcn = cid.mUarfcn; } CellIdentityWcdma copy() { @@ -123,6 +143,13 @@ public final class CellIdentityWcdma implements Parcelable { return Objects.hash(mMcc, mMnc, mLac, mCid, mPsc); } + /** + * @return 16-bit UMTS Absolute RF Channel Number, Integer.MAX_VALUE if unknown + */ + public int getUarfcn() { + return mUarfcn; + } + @Override public boolean equals(Object other) { if (this == other) { @@ -138,7 +165,8 @@ public final class CellIdentityWcdma implements Parcelable { mMnc == o.mMnc && mLac == o.mLac && mCid == o.mCid && - mPsc == o.mPsc; + mPsc == o.mPsc && + mUarfcn == o.mUarfcn; } @Override @@ -149,6 +177,7 @@ public final class CellIdentityWcdma implements Parcelable { sb.append(" mLac=").append(mLac); sb.append(" mCid=").append(mCid); sb.append(" mPsc=").append(mPsc); + sb.append(" mUarfcn=").append(mUarfcn); sb.append("}"); return sb.toString(); @@ -169,6 +198,7 @@ public final class CellIdentityWcdma implements Parcelable { dest.writeInt(mLac); dest.writeInt(mCid); dest.writeInt(mPsc); + dest.writeInt(mUarfcn); } /** Construct from Parcel, type has already been processed */ @@ -178,6 +208,7 @@ public final class CellIdentityWcdma implements Parcelable { mLac = in.readInt(); mCid = in.readInt(); mPsc = in.readInt(); + mUarfcn = in.readInt(); if (DBG) log("CellIdentityWcdma(Parcel): " + toString()); } diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java index d27fcec38351..addf7ef025aa 100644 --- a/telephony/java/android/telephony/CellSignalStrengthGsm.java +++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java @@ -34,6 +34,7 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P private int mSignalStrength; // Valid values are (0-31, 99) as defined in TS 27.007 8.5 private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 + private int mTimingAdvance; /** * Empty constructor @@ -75,6 +76,22 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P public void initialize(int ss, int ber) { mSignalStrength = ss; mBitErrorRate = ber; + mTimingAdvance = Integer.MAX_VALUE; + } + + /** + * Initialize all the values + * + * @param ss SignalStrength as ASU value + * @param ber is Bit Error Rate + * @param ta timing advance + * + * @hide + */ + public void initialize(int ss, int ber, int ta) { + mSignalStrength = ss; + mBitErrorRate = ber; + mTimingAdvance = ta; } /** @@ -83,6 +100,7 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P protected void copyFrom(CellSignalStrengthGsm s) { mSignalStrength = s.mSignalStrength; mBitErrorRate = s.mBitErrorRate; + mTimingAdvance = s.mTimingAdvance; } /** @@ -98,6 +116,7 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P public void setDefaultValues() { mSignalStrength = Integer.MAX_VALUE; mBitErrorRate = Integer.MAX_VALUE; + mTimingAdvance = Integer.MAX_VALUE; } /** @@ -174,7 +193,8 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P return false; } - return mSignalStrength == s.mSignalStrength && mBitErrorRate == s.mBitErrorRate; + return mSignalStrength == s.mSignalStrength && mBitErrorRate == s.mBitErrorRate && + s.mTimingAdvance == mTimingAdvance; } /** @@ -184,7 +204,8 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P public String toString() { return "CellSignalStrengthGsm:" + " ss=" + mSignalStrength - + " ber=" + mBitErrorRate; + + " ber=" + mBitErrorRate + + " mTa=" + mTimingAdvance; } /** Implement the Parcelable interface */ @@ -193,6 +214,7 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P if (DBG) log("writeToParcel(Parcel, int): " + toString()); dest.writeInt(mSignalStrength); dest.writeInt(mBitErrorRate); + dest.writeInt(mTimingAdvance); } /** @@ -202,6 +224,7 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P private CellSignalStrengthGsm(Parcel in) { mSignalStrength = in.readInt(); mBitErrorRate = in.readInt(); + mTimingAdvance = in.readInt(); if (DBG) log("CellSignalStrengthGsm(Parcel): " + toString()); } |