Merge "Add xhdpi resources for large mouse pointers"
diff --git a/api/current.txt b/api/current.txt
index b7f63bc..230d22e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13375,6 +13375,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 55f08c8..e4f42c0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -13772,6 +13772,7 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 e320b67..1a9b469 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -13383,6 +13383,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 560e22a..fa11234 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -23,4 +23,11 @@
/** 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 2bbc54f..999d826 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -155,7 +155,7 @@
}
/**
- * @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 0168908..654396b 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -176,7 +176,7 @@
/**
* 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 6935174..e5efd56 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -551,6 +551,29 @@
*/
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 @@
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 9937b2c..416c74c 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -497,6 +497,40 @@
* <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 6c3f308..dd0887f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -100,13 +100,13 @@
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 @@
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 @@
* <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 @@
* <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 @@
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;
+ // move it from child's content coordinate space to parent's content coordinate space
+ position.offset(child.mLeft - child.getScrollX(), child.mTop -child.getScrollY());
- position.offset(-parentView.getScrollX(), -parentView.getScrollY());
-
- child = parentView;
+ child = (View) parent;
parent = child.getParent();
}
@@ -9718,6 +9797,9 @@
}
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 @@
}
}
+ 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 @@
}
/**
+ * 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 @@
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
-
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
@@ -14278,6 +14468,45 @@
}
}
+ 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 @@
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 @@
* @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 @@
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 27e2ea3..3fe6b8e 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -59,6 +59,7 @@
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 @@
@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 @@
* 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 @@
* 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 @@
* 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 e9b123b5..1962be8 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -266,14 +266,14 @@
* 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 0d6f362..5d41477 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6729,16 +6729,19 @@
@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 6c2c956..9561f08 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -3701,6 +3701,13 @@
}
}
+ /** @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 4355eb3..67473c6 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1082,6 +1082,20 @@
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 @@
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 @@
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 ebc7eb3..f16fdd6 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -454,6 +454,10 @@
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 91d6232..8880217 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 @@
}
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;
+ final int thumbLength =
+ ScrollBarUtils.getThumbLength(scrollBarLength, thickness, extent, range);
+ final int thumbOffset =
+ ScrollBarUtils.getThumbOffset(scrollBarLength, thumbLength, extent, range,
+ mOffset);
- // 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;
- }
-
- 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 78b931d..3f7a07b 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -489,6 +489,10 @@
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 0000000..0ae9f74
--- /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 5a08887..cf7978c 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 c0453f8..eb0075b 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 afd0bc4..00df87d 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.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 @@
}
@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 edbfef9..5dae4a8 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -159,6 +159,22 @@
}
@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 86c0e5d..8ef5ca0 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -43,4 +43,10 @@
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 f8057db..0febc16 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -55,7 +55,8 @@
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 @@
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 6792680..3bf415b 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 7a853d1..bd05184 100644
--- a/media/java/android/media/tv/ITvInputService.aidl
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -27,10 +27,11 @@
* @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 74ab562..9f13882 100644
--- a/media/java/android/media/tv/ITvInputServiceCallback.aidl
+++ b/media/java/android/media/tv/ITvInputServiceCallback.aidl
@@ -27,4 +27,6 @@
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 6a06b8f..408a762 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -48,9 +48,16 @@
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 e936810..cb6a05e 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -40,4 +40,10 @@
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 f8c6f3f..4ac5876 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -59,20 +59,29 @@
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 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 @@
}
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 @@
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 @@
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 @@
}
@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 @@
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 7cd086e..62a01dc 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -54,6 +54,7 @@
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 @@
}
/**
+ * 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 @@
*
* <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 @@
}
/**
+ * 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 @@
/**
* 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 a3d748e..3960230 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -419,6 +419,27 @@
}
/**
+ * 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 6a13f82..f1de8fd 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 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 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 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 @@
}
});
}
+
+ // 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 @@
/**
* 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 @@
* <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 @@
* <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 @@
* <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,15 +748,6 @@
return mCallback;
}
- public void postInputStateChanged(final String inputId, final int state) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mCallback.onInputStateChanged(inputId, state);
- }
- });
- }
-
public void postInputAdded(final String inputId) {
mHandler.post(new Runnable() {
@Override
@@ -669,6 +774,24 @@
}
});
}
+
+ public void postInputStateChanged(final String inputId, final int state) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ 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 @@
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 @@
}
}
}
+
+ @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 @@
* <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 @@
* <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 @@
@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 @@
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 @@
* 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 @@
/**
* 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 @@
* 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 @@
}
/**
+ * 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 @@
}
/**
+ * 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 053d43b6..6d9b1ad 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 @@
}
@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 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 @@
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 @@
@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 @@
* @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 @@
// 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 @@
* @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 @@
* @see #notifyVideoUnavailable
*/
public void notifyVideoAvailable() {
- executeOrPostRunnable(new Runnable() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
@Override
public void run() {
try {
@@ -478,7 +525,8 @@
|| 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 @@
* @see TvInputManager
*/
public void notifyContentAllowed() {
- executeOrPostRunnable(new Runnable() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
@Override
public void run() {
try {
@@ -562,7 +611,8 @@
*/
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 @@
* </ul>
*/
public void notifyTimeShiftStatusChanged(final int status) {
- executeOrPostRunnable(new Runnable() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
@Override
public void run() {
try {
@@ -619,7 +670,8 @@
}
private void notifyTimeShiftStartPositionChanged(final long timeMs) {
- executeOrPostRunnable(new Runnable() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
@Override
public void run() {
try {
@@ -635,7 +687,8 @@
}
private void notifyTimeShiftCurrentPositionChanged(final long timeMs) {
- executeOrPostRunnable(new Runnable() {
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
@Override
public void run() {
try {
@@ -665,7 +718,8 @@
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 @@
}
/**
+ * 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 @@
/**
* 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 @@
* 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 @@
* 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 @@
* 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 @@
* 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 @@
}
/**
+ * Calls {@link #onTimeShiftPlay(Uri)}.
+ */
+ void timeShiftPlay(Uri recordedProgramUri) {
+ mCurrentPositionMs = 0;
+ onTimeShiftPlay(recordedProgramUri);
+ }
+
+ /**
* Calls {@link #onTimeShiftPause}.
*/
void timeShiftPause() {
@@ -1385,7 +1467,7 @@
}
}
- 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 @@
}
/**
+ * 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 @@
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 @@
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 @@
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 @@
}
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 0000000..865e000
--- /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 003a274..0132d24 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -448,6 +448,37 @@
}
/**
+ * 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 @@
final String mInputId;
Uri mChannelUri;
Bundle mTuneParams;
+ Uri mRecordedProgramUri;
MySessionCallback(String inputId, Uri channelUri, Bundle tuneParams) {
mInputId = inputId;
@@ -1001,6 +1033,11 @@
mTuneParams = tuneParams;
}
+ MySessionCallback(String inputId, Uri recordedProgramUri) {
+ mInputId = inputId;
+ mRecordedProgramUri = recordedProgramUri;
+ }
+
@Override
public void onSessionCreated(Session session) {
if (DEBUG) {
@@ -1043,7 +1080,11 @@
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 46267a2..15b2a97 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 9a1d6a4..811751c 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 0000000..284827b
--- /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 0000000..426238c
--- /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 0000000..0713548
--- /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 814e7ea..faf36ec 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 6e22dde..543a2f3 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 @@
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 0000000..1aeb0fe
--- /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 881aa6a..5cd540b 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 @@
*/
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 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 f8cbf65..5f11bee 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 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 3f52ae8..87cfcff 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 @@
// 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 @@
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 @@
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 @@
/** 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 @@
/** 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 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 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 @@
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 @@
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 @@
* 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 @@
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 @@
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 @@
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 @@
/** 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 9cdd703..d15828a 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.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.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 @@
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 de1daa8..66eeac6 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 @@
* 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 @@
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 @@
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 0000000..9f2b00a
--- /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 3f5d0a8..f5ab01f 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.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 fe9c68e..ccc8581 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 @@
// 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 @@
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 @@
// 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 32bebb3..5a4064a 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.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 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 @@
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 827ee40..cb108da 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 @@
* 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 39d0604..de96d9d 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 @@
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 a8e09f8..e20936b 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 @@
}
@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 d0c14f1..ed6d940 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 @@
@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 7e27856..9c2159be 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.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 @@
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 0000000..186e8b5
--- /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 b2bcde3..755a5b3 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 @@
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 3385c82..dbb0295 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 @@
private final SubscriptionManager mSubscriptionManager;
private final boolean mHasMobileDataFeature;
private final SubscriptionDefaults mSubDefaults;
+ private final DataSaverController mDataSaverController;
private Config mConfig;
// Subcontrollers.
@@ -156,6 +157,7 @@
mConfig = config;
mReceiverHandler = new Handler(bgLooper);
mCallbackHandler = callbackHandler;
+ mDataSaverController = new DataSaverController(context);
mSubscriptionManager = subManager;
mSubDefaults = defaultsHandler;
@@ -189,6 +191,10 @@
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 bed1e9e..b237400 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.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 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 @@
// 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 88e55dd..697777c 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -339,5 +339,8 @@
// 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 a4876b9..a71ba636 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 @@
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 @@
}
/**
+ * @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 3668ddd..4788223 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -18,17 +18,18 @@
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 @@
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 84f980d..2b06780 100644
--- a/rs/java/android/renderscript/Script.java
+++ b/rs/java/android/renderscript/Script.java
@@ -315,7 +315,6 @@
/**
* 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 3954070..398d89b 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -2752,7 +2752,43 @@
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 @@
{"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 1434e5e..b8327c1 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.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 @@
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 @@
}
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 @@
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 23dc0f6..7b7359f 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 @@
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 @@
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 @@
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 3193ff8..3ca99d4 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 @@
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 @@
// 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 @@
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 @@
}
}
+ 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 @@
@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 @@
// 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 @@
}
@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 @@
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 @@
}
@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 @@
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 @@
// 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 @@
}
}
}
+
+ @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 @@
}
}
}
+
+ // 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 90d2aa0..74f1171 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -38,6 +38,10 @@
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 @@
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 @@
* @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 @@
mMnc = cid.mMnc;
mLac = cid.mLac;
mCid = cid.mCid;
+ mArfcn = cid.mArfcn;
+ mBsic = cid.mBsic;
}
CellIdentityGsm copy() {
@@ -106,6 +131,21 @@
}
/**
+ * @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 @@
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 @@
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 @@
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 @@
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 1e7ac08..ce74383 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -40,6 +40,8 @@
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 @@
mCi = Integer.MAX_VALUE;
mPci = Integer.MAX_VALUE;
mTac = Integer.MAX_VALUE;
+ mEarfcn = Integer.MAX_VALUE;
}
/**
@@ -63,11 +66,27 @@
* @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 @@
mCi = cid.mCi;
mPci = cid.mPci;
mTac = cid.mTac;
+ mEarfcn = cid.mEarfcn;
}
CellIdentityLte copy() {
@@ -117,6 +137,13 @@
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 @@
mMnc == o.mMnc &&
mCi == o.mCi &&
mPci == o.mPci &&
- mTac == o.mTac;
+ mTac == o.mTac &&
+ mEarfcn == o.mEarfcn;
}
@Override
@@ -148,6 +176,7 @@
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 @@
dest.writeInt(mCi);
dest.writeInt(mPci);
dest.writeInt(mTac);
+ dest.writeInt(mEarfcn);
}
/** Construct from Parcel, type has already been processed */
@@ -177,6 +207,7 @@
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 56ee8c9..0d13efd 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -40,6 +40,8 @@
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 @@
mLac = Integer.MAX_VALUE;
mCid = Integer.MAX_VALUE;
mPsc = Integer.MAX_VALUE;
+ mUarfcn = Integer.MAX_VALUE;
}
/**
* public constructor
@@ -62,11 +65,27 @@
* @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 @@
mLac = cid.mLac;
mCid = cid.mCid;
mPsc = cid.mPsc;
+ mUarfcn = cid.mUarfcn;
}
CellIdentityWcdma copy() {
@@ -123,6 +143,13 @@
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 @@
mMnc == o.mMnc &&
mLac == o.mLac &&
mCid == o.mCid &&
- mPsc == o.mPsc;
+ mPsc == o.mPsc &&
+ mUarfcn == o.mUarfcn;
}
@Override
@@ -149,6 +177,7 @@
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 @@
dest.writeInt(mLac);
dest.writeInt(mCid);
dest.writeInt(mPsc);
+ dest.writeInt(mUarfcn);
}
/** Construct from Parcel, type has already been processed */
@@ -178,6 +208,7 @@
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 d27fcec..addf7ef 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -34,6 +34,7 @@
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 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 @@
protected void copyFrom(CellSignalStrengthGsm s) {
mSignalStrength = s.mSignalStrength;
mBitErrorRate = s.mBitErrorRate;
+ mTimingAdvance = s.mTimingAdvance;
}
/**
@@ -98,6 +116,7 @@
public void setDefaultValues() {
mSignalStrength = Integer.MAX_VALUE;
mBitErrorRate = Integer.MAX_VALUE;
+ mTimingAdvance = Integer.MAX_VALUE;
}
/**
@@ -174,7 +193,8 @@
return false;
}
- return mSignalStrength == s.mSignalStrength && mBitErrorRate == s.mBitErrorRate;
+ return mSignalStrength == s.mSignalStrength && mBitErrorRate == s.mBitErrorRate &&
+ s.mTimingAdvance == mTimingAdvance;
}
/**
@@ -184,7 +204,8 @@
public String toString() {
return "CellSignalStrengthGsm:"
+ " ss=" + mSignalStrength
- + " ber=" + mBitErrorRate;
+ + " ber=" + mBitErrorRate
+ + " mTa=" + mTimingAdvance;
}
/** Implement the Parcelable interface */
@@ -193,6 +214,7 @@
if (DBG) log("writeToParcel(Parcel, int): " + toString());
dest.writeInt(mSignalStrength);
dest.writeInt(mBitErrorRate);
+ dest.writeInt(mTimingAdvance);
}
/**
@@ -202,6 +224,7 @@
private CellSignalStrengthGsm(Parcel in) {
mSignalStrength = in.readInt();
mBitErrorRate = in.readInt();
+ mTimingAdvance = in.readInt();
if (DBG) log("CellSignalStrengthGsm(Parcel): " + toString());
}