Merge "Deal gracefully with cancel of nonexistent jobs"
diff --git a/Android.bp b/Android.bp
index 04e1dd6..ee2281f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -243,6 +243,7 @@
"core/java/android/os/storage/IStorageEventListener.aidl",
"core/java/android/os/storage/IStorageShutdownObserver.aidl",
"core/java/android/os/storage/IObbActionListener.aidl",
+ "core/java/android/security/IConfirmationPromptCallback.aidl",
"core/java/android/security/IKeystoreService.aidl",
"core/java/android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl",
"core/java/android/service/autofill/IAutoFillService.aidl",
@@ -473,8 +474,8 @@
"telecomm/java/com/android/internal/telecom/IInCallService.aidl",
"telecomm/java/com/android/internal/telecom/ITelecomService.aidl",
"telecomm/java/com/android/internal/telecom/RemoteServiceCallback.aidl",
- "telephony/java/android/telephony/data/IDataService.aidl",
- "telephony/java/android/telephony/data/IDataServiceCallback.aidl",
+ "telephony/java/android/telephony/data/IDataService.aidl",
+ "telephony/java/android/telephony/data/IDataServiceCallback.aidl",
"telephony/java/android/telephony/ims/internal/aidl/IImsCallSessionListener.aidl",
"telephony/java/android/telephony/ims/internal/aidl/IImsCapabilityCallback.aidl",
"telephony/java/android/telephony/ims/internal/aidl/IImsConfig.aidl",
@@ -484,13 +485,14 @@
"telephony/java/android/telephony/ims/internal/aidl/IImsRcsFeature.aidl",
"telephony/java/android/telephony/ims/internal/aidl/IImsServiceController.aidl",
"telephony/java/android/telephony/ims/internal/aidl/IImsServiceControllerListener.aidl",
- "telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl",
- "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl",
+ "telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl",
"telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl",
"telephony/java/android/telephony/mbms/IDownloadStateCallback.aidl",
"telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl",
"telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl",
"telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl",
+ "telephony/java/android/telephony/INetworkService.aidl",
+ "telephony/java/android/telephony/INetworkServiceCallback.aidl",
"telephony/java/com/android/ims/internal/IImsCallSession.aidl",
"telephony/java/com/android/ims/internal/IImsCallSessionListener.aidl",
"telephony/java/com/android/ims/internal/IImsConfig.aidl",
@@ -663,7 +665,9 @@
],
// Loaded with System.loadLibrary by android.view.textclassifier
- required: ["libtextclassifier"],
+ required: [
+ "libtextclassifier",
+ "libmedia2_jni",],
javac_shard_size: 150,
@@ -825,7 +829,9 @@
srcs: [
"core/java/android/os/HidlSupport.java",
+ "core/java/android/annotation/IntDef.java",
"core/java/android/annotation/NonNull.java",
+ "core/java/android/annotation/SystemApi.java",
"core/java/android/os/HwBinder.java",
"core/java/android/os/HwBlob.java",
"core/java/android/os/HwParcel.java",
diff --git a/Android.mk b/Android.mk
index 2254008..32e4bfa 100644
--- a/Android.mk
+++ b/Android.mk
@@ -823,6 +823,28 @@
$(call all-proto-files-under, libs/incident/proto/android/os)
include $(BUILD_STATIC_JAVA_LIBRARY)
+# ==== hiddenapi lists =======================================
+
+# Generate light greylist as private API minus (blacklist plus dark greylist).
+
+$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): PRIVATE_API := $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE)
+$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): BLACKLIST := $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST)
+$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): DARK_GREYLIST := $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)
+$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE) \
+ $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST) \
+ $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)
+ if [ ! -z "`comm -12 <(sort $(BLACKLIST)) <(sort $(DARK_GREYLIST))`" ]; then \
+ echo "There should be no overlap between $(BLACKLIST) and $(DARK_GREYLIST)" 1>&2; \
+ exit 1; \
+ elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(BLACKLIST))`" ]; then \
+ echo "$(BLACKLIST) must be a subset of $(PRIVATE_API)" 1>&2; \
+ exit 2; \
+ elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(DARK_GREYLIST))`" ]; then \
+ echo "$(DARK_GREYLIST) must be a subset of $(PRIVATE_API)" 1>&2; \
+ exit 3; \
+ fi
+ comm -23 <(sort $(PRIVATE_API)) <(sort $(BLACKLIST) $(DARK_GREYLIST)) > $@
+
# Include subdirectory makefiles
# ============================================================
diff --git a/api/current.txt b/api/current.txt
index 5e91789..206d377 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6,6 +6,7 @@
public static final class Manifest.permission {
ctor public Manifest.permission();
+ field public static final java.lang.String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER";
field public static final java.lang.String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
field public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
field public static final java.lang.String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
@@ -1810,6 +1811,7 @@
public static final class R.id {
ctor public R.id();
field public static final int accessibilityActionContextClick = 16908348; // 0x102003c
+ field public static final int accessibilityActionHideTooltip = 16908357; // 0x1020045
field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042
field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a
field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039
@@ -1818,6 +1820,7 @@
field public static final int accessibilityActionScrollUp = 16908344; // 0x1020038
field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d
field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036
+ field public static final int accessibilityActionShowTooltip = 16908356; // 0x1020044
field public static final int addToDictionary = 16908330; // 0x102002a
field public static final int autofill = 16908355; // 0x1020043
field public static final int background = 16908288; // 0x1020000
@@ -5224,6 +5227,7 @@
field public static final java.lang.String EXTRA_PROGRESS = "android.progress";
field public static final java.lang.String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
field public static final java.lang.String EXTRA_PROGRESS_MAX = "android.progressMax";
+ field public static final java.lang.String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
field public static final java.lang.String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
field public static final deprecated java.lang.String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
field public static final java.lang.String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
@@ -6890,6 +6894,8 @@
method public void onRestore(android.app.backup.BackupDataInput, long, android.os.ParcelFileDescriptor) throws java.io.IOException;
method public void onRestoreFile(android.os.ParcelFileDescriptor, long, java.io.File, int, long, long) throws java.io.IOException;
method public void onRestoreFinished();
+ field public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1; // 0x1
+ field public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2; // 0x2
field public static final int TYPE_DIRECTORY = 2; // 0x2
field public static final int TYPE_FILE = 1; // 0x1
}
@@ -6917,6 +6923,7 @@
public class BackupDataOutput {
method public long getQuota();
+ method public int getTransportFlags();
method public int writeEntityData(byte[], int) throws java.io.IOException;
method public int writeEntityHeader(java.lang.String, int) throws java.io.IOException;
}
@@ -6931,7 +6938,7 @@
ctor public BackupManager(android.content.Context);
method public void dataChanged();
method public static void dataChanged(java.lang.String);
- method public int requestRestore(android.app.backup.RestoreObserver);
+ method public deprecated int requestRestore(android.app.backup.RestoreObserver);
}
public class FileBackupHelper implements android.app.backup.BackupHelper {
@@ -6942,6 +6949,7 @@
public class FullBackupDataOutput {
method public long getQuota();
+ method public int getTransportFlags();
}
public abstract class RestoreObserver {
@@ -11138,6 +11146,7 @@
field public static final java.lang.String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
field public static final java.lang.String FEATURE_CAMERA = "android.hardware.camera";
field public static final java.lang.String FEATURE_CAMERA_ANY = "android.hardware.camera.any";
+ field public static final java.lang.String FEATURE_CAMERA_AR = "android.hardware.camera.ar";
field public static final java.lang.String FEATURE_CAMERA_AUTOFOCUS = "android.hardware.camera.autofocus";
field public static final java.lang.String FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING = "android.hardware.camera.capability.manual_post_processing";
field public static final java.lang.String FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR = "android.hardware.camera.capability.manual_sensor";
@@ -11197,6 +11206,7 @@
field public static final java.lang.String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector";
field public static final java.lang.String FEATURE_SIP = "android.software.sip";
field public static final java.lang.String FEATURE_SIP_VOIP = "android.software.sip.voip";
+ field public static final java.lang.String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore";
field public static final java.lang.String FEATURE_TELEPHONY = "android.hardware.telephony";
field public static final java.lang.String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
field public static final java.lang.String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
@@ -11551,15 +11561,15 @@
public final class AssetManager implements java.lang.AutoCloseable {
method public void close();
- method public final java.lang.String[] getLocales();
- method public final java.lang.String[] list(java.lang.String) throws java.io.IOException;
- method public final java.io.InputStream open(java.lang.String) throws java.io.IOException;
- method public final java.io.InputStream open(java.lang.String, int) throws java.io.IOException;
- method public final android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException;
- method public final android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException;
- method public final android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException;
- method public final android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException;
- method public final android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException;
+ method public java.lang.String[] getLocales();
+ method public java.lang.String[] list(java.lang.String) throws java.io.IOException;
+ method public java.io.InputStream open(java.lang.String) throws java.io.IOException;
+ method public java.io.InputStream open(java.lang.String, int) throws java.io.IOException;
+ method public android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException;
+ method public android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException;
+ method public android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException;
+ method public android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException;
+ method public android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException;
field public static final int ACCESS_BUFFER = 3; // 0x3
field public static final int ACCESS_RANDOM = 1; // 0x1
field public static final int ACCESS_STREAMING = 2; // 0x2
@@ -16221,6 +16231,7 @@
public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult {
method public java.util.List<android.hardware.camera2.CaptureResult> getPartialResults();
+ method public <T> T getPhysicalCameraKey(android.hardware.camera2.CaptureResult.Key<T>, java.lang.String);
}
}
@@ -22090,6 +22101,7 @@
method public int getChannelConfiguration();
method public int getChannelCount();
method public android.media.AudioFormat getFormat();
+ method public android.os.PersistableBundle getMetrics();
method public static int getMinBufferSize(int, int, int);
method public int getNotificationMarkerPosition();
method public int getPositionNotificationPeriod();
@@ -22138,6 +22150,14 @@
method public android.media.AudioRecord.Builder setBufferSizeInBytes(int) throws java.lang.IllegalArgumentException;
}
+ public static final class AudioRecord.MetricsConstants {
+ field public static final java.lang.String CHANNELS = "android.media.audiorecord.channels";
+ field public static final java.lang.String ENCODING = "android.media.audiorecord.encoding";
+ field public static final java.lang.String LATENCY = "android.media.audiorecord.latency";
+ field public static final java.lang.String SAMPLERATE = "android.media.audiorecord.samplerate";
+ field public static final java.lang.String SOURCE = "android.media.audiorecord.source";
+ }
+
public static abstract interface AudioRecord.OnRecordPositionUpdateListener {
method public abstract void onMarkerReached(android.media.AudioRecord);
method public abstract void onPeriodicNotification(android.media.AudioRecord);
@@ -22197,6 +22217,7 @@
method public int getChannelCount();
method public android.media.AudioFormat getFormat();
method public static float getMaxVolume();
+ method public android.os.PersistableBundle getMetrics();
method public static int getMinBufferSize(int, int, int);
method public static float getMinVolume();
method protected deprecated int getNativeFrameCount();
@@ -22277,6 +22298,14 @@
method public android.media.AudioTrack.Builder setTransferMode(int) throws java.lang.IllegalArgumentException;
}
+ public static final class AudioTrack.MetricsConstants {
+ field public static final java.lang.String CHANNELMASK = "android.media.audiorecord.channelmask";
+ field public static final java.lang.String CONTENTTYPE = "android.media.audiotrack.type";
+ field public static final java.lang.String SAMPLERATE = "android.media.audiorecord.samplerate";
+ field public static final java.lang.String STREAMTYPE = "android.media.audiotrack.streamtype";
+ field public static final java.lang.String USAGE = "android.media.audiotrack.usage";
+ }
+
public static abstract interface AudioTrack.OnPlaybackPositionUpdateListener {
method public abstract void onMarkerReached(android.media.AudioTrack);
method public abstract void onPeriodicNotification(android.media.AudioTrack);
@@ -22345,6 +22374,40 @@
field public static final int QUALITY_MEDIUM = 1; // 0x1
}
+ public final class DataSourceDesc {
+ method public long getEndPosition();
+ method public java.io.FileDescriptor getFileDescriptor();
+ method public long getFileDescriptorLength();
+ method public long getFileDescriptorOffset();
+ method public long getId();
+ method public android.media.Media2DataSource getMedia2DataSource();
+ method public long getStartPosition();
+ method public int getType();
+ method public android.net.Uri getUri();
+ method public android.content.Context getUriContext();
+ method public java.util.List<java.net.HttpCookie> getUriCookies();
+ method public java.util.Map<java.lang.String, java.lang.String> getUriHeaders();
+ field public static final long LONG_MAX = 576460752303423487L; // 0x7ffffffffffffffL
+ field public static final int TYPE_CALLBACK = 1; // 0x1
+ field public static final int TYPE_FD = 2; // 0x2
+ field public static final int TYPE_NONE = 0; // 0x0
+ field public static final int TYPE_URI = 3; // 0x3
+ }
+
+ public static class DataSourceDesc.Builder {
+ ctor public DataSourceDesc.Builder();
+ ctor public DataSourceDesc.Builder(android.media.DataSourceDesc);
+ method public android.media.DataSourceDesc build();
+ method public android.media.DataSourceDesc.Builder setDataSource(android.media.Media2DataSource);
+ method public android.media.DataSourceDesc.Builder setDataSource(java.io.FileDescriptor);
+ method public android.media.DataSourceDesc.Builder setDataSource(java.io.FileDescriptor, long, long);
+ method public android.media.DataSourceDesc.Builder setDataSource(android.content.Context, android.net.Uri);
+ method public android.media.DataSourceDesc.Builder setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>, java.util.List<java.net.HttpCookie>);
+ method public android.media.DataSourceDesc.Builder setEndPosition(long);
+ method public android.media.DataSourceDesc.Builder setId(long);
+ method public android.media.DataSourceDesc.Builder setStartPosition(long);
+ }
+
public final class DeniedByServerException extends android.media.MediaDrmException {
ctor public DeniedByServerException(java.lang.String);
}
@@ -22621,6 +22684,12 @@
method public abstract void onJetUserIdUpdate(android.media.JetPlayer, int, int);
}
+ public abstract class Media2DataSource implements java.io.Closeable {
+ ctor public Media2DataSource();
+ method public abstract long getSize() throws java.io.IOException;
+ method public abstract int readAt(long, byte[], int, int) throws java.io.IOException;
+ }
+
public class MediaActionSound {
ctor public MediaActionSound();
method public void load(int);
@@ -23075,6 +23144,7 @@
public static final class MediaCodecInfo.EncoderCapabilities {
method public android.util.Range<java.lang.Integer> getComplexityRange();
+ method public android.util.Range<java.lang.Integer> getQualityRange();
method public boolean isBitrateModeSupported(int);
field public static final int BITRATE_MODE_CBR = 2; // 0x2
field public static final int BITRATE_MODE_CQ = 0; // 0x0
@@ -23179,6 +23249,7 @@
method public android.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.HashMap<java.lang.String, java.lang.String>) throws android.media.NotProvisionedException;
method public int getMaxHdcpLevel();
method public int getMaxSessionCount();
+ method public android.os.PersistableBundle getMetrics();
method public int getOpenSessionCount();
method public byte[] getPropertyByteArray(java.lang.String);
method public java.lang.String getPropertyString(java.lang.String);
@@ -23266,6 +23337,44 @@
method public java.lang.String getDiagnosticInfo();
}
+ public static final class MediaDrm.MetricsConstants {
+ field public static final java.lang.String CLOSE_SESSION_ERROR_COUNT = "drm.mediadrm.close_session.error.count";
+ field public static final java.lang.String CLOSE_SESSION_ERROR_LIST = "drm.mediadrm.close_session.error.list";
+ field public static final java.lang.String CLOSE_SESSION_OK_COUNT = "drm.mediadrm.close_session.ok.count";
+ field public static final java.lang.String EVENT_KEY_EXPIRED_COUNT = "drm.mediadrm.event.KEY_EXPIRED.count";
+ field public static final java.lang.String EVENT_KEY_NEEDED_COUNT = "drm.mediadrm.event.KEY_NEEDED.count";
+ field public static final java.lang.String EVENT_PROVISION_REQUIRED_COUNT = "drm.mediadrm.event.PROVISION_REQUIRED.count";
+ field public static final java.lang.String EVENT_SESSION_RECLAIMED_COUNT = "drm.mediadrm.event.SESSION_RECLAIMED.count";
+ field public static final java.lang.String EVENT_VENDOR_DEFINED_COUNT = "drm.mediadrm.event.VENDOR_DEFINED.count";
+ field public static final java.lang.String GET_DEVICE_UNIQUE_ID_ERROR_COUNT = "drm.mediadrm.get_device_unique_id.error.count";
+ field public static final java.lang.String GET_DEVICE_UNIQUE_ID_ERROR_LIST = "drm.mediadrm.get_device_unique_id.error.list";
+ field public static final java.lang.String GET_DEVICE_UNIQUE_ID_OK_COUNT = "drm.mediadrm.get_device_unique_id.ok.count";
+ field public static final java.lang.String GET_KEY_REQUEST_ERROR_COUNT = "drm.mediadrm.get_key_request.error.count";
+ field public static final java.lang.String GET_KEY_REQUEST_ERROR_LIST = "drm.mediadrm.get_key_request.error.list";
+ field public static final java.lang.String GET_KEY_REQUEST_OK_COUNT = "drm.mediadrm.get_key_request.ok.count";
+ field public static final java.lang.String GET_KEY_REQUEST_OK_TIME_MICROS = "drm.mediadrm.get_key_request.ok.average_time_micros";
+ field public static final java.lang.String GET_PROVISION_REQUEST_ERROR_COUNT = "drm.mediadrm.get_provision_request.error.count";
+ field public static final java.lang.String GET_PROVISION_REQUEST_ERROR_LIST = "drm.mediadrm.get_provision_request.error.list";
+ field public static final java.lang.String GET_PROVISION_REQUEST_OK_COUNT = "drm.mediadrm.get_provision_request.ok.count";
+ field public static final java.lang.String KEY_STATUS_EXPIRED_COUNT = "drm.mediadrm.key_status.EXPIRED.count";
+ field public static final java.lang.String KEY_STATUS_INTERNAL_ERROR_COUNT = "drm.mediadrm.key_status.INTERNAL_ERROR.count";
+ field public static final java.lang.String KEY_STATUS_OUTPUT_NOT_ALLOWED_COUNT = "drm.mediadrm.key_status_change.OUTPUT_NOT_ALLOWED.count";
+ field public static final java.lang.String KEY_STATUS_PENDING_COUNT = "drm.mediadrm.key_status_change.PENDING.count";
+ field public static final java.lang.String KEY_STATUS_USABLE_COUNT = "drm.mediadrm.key_status_change.USABLE.count";
+ field public static final java.lang.String OPEN_SESSION_ERROR_COUNT = "drm.mediadrm.open_session.error.count";
+ field public static final java.lang.String OPEN_SESSION_ERROR_LIST = "drm.mediadrm.open_session.error.list";
+ field public static final java.lang.String OPEN_SESSION_OK_COUNT = "drm.mediadrm.open_session.ok.count";
+ field public static final java.lang.String PROVIDE_KEY_RESPONSE_ERROR_COUNT = "drm.mediadrm.provide_key_response.error.count";
+ field public static final java.lang.String PROVIDE_KEY_RESPONSE_ERROR_LIST = "drm.mediadrm.provide_key_response.error.list";
+ field public static final java.lang.String PROVIDE_KEY_RESPONSE_OK_COUNT = "drm.mediadrm.provide_key_response.ok.count";
+ field public static final java.lang.String PROVIDE_KEY_RESPONSE_OK_TIME_MICROS = "drm.mediadrm.provide_key_response.ok.average_time_micros";
+ field public static final java.lang.String PROVIDE_PROVISION_RESPONSE_ERROR_COUNT = "drm.mediadrm.provide_provision_response.error.count";
+ field public static final java.lang.String PROVIDE_PROVISION_RESPONSE_ERROR_LIST = "drm.mediadrm.provide_provision_response.error.list";
+ field public static final java.lang.String PROVIDE_PROVISION_RESPONSE_OK_COUNT = "drm.mediadrm.provide_provision_response.ok.count";
+ field public static final java.lang.String SESSION_END_TIMES_MS = "drm.mediadrm.session_end_times_ms";
+ field public static final java.lang.String SESSION_START_TIMES_MS = "drm.mediadrm.session_start_times_ms";
+ }
+
public static abstract interface MediaDrm.OnEventListener {
method public abstract void onEvent(android.media.MediaDrm, byte[], int, int, byte[]);
}
@@ -23305,6 +23414,7 @@
method public java.util.Map<java.util.UUID, byte[]> getPsshInfo();
method public boolean getSampleCryptoInfo(android.media.MediaCodec.CryptoInfo);
method public int getSampleFlags();
+ method public long getSampleSize();
method public long getSampleTime();
method public int getSampleTrackIndex();
method public final int getTrackCount();
@@ -23417,6 +23527,7 @@
field public static final java.lang.String KEY_PRIORITY = "priority";
field public static final java.lang.String KEY_PROFILE = "profile";
field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
+ field public static final java.lang.String KEY_QUALITY = "quality";
field public static final java.lang.String KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after";
field public static final java.lang.String KEY_ROTATION = "rotation-degrees";
field public static final java.lang.String KEY_SAMPLE_RATE = "sample-rate";
@@ -23817,6 +23928,169 @@
field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
}
+ public abstract class MediaPlayer2 implements android.media.AudioRouting java.lang.AutoCloseable {
+ method public abstract void addPlaylistItem(int, android.media.DataSourceDesc);
+ method public abstract void attachAuxEffect(int);
+ method public abstract void clearPendingCommands();
+ method public abstract void close();
+ method public static final android.media.MediaPlayer2 create();
+ method public abstract void deselectTrack(int);
+ method public abstract android.media.DataSourceDesc editPlaylistItem(int, android.media.DataSourceDesc);
+ method public abstract int getAudioSessionId();
+ method public abstract android.media.DataSourceDesc getCurrentDataSource();
+ method public abstract int getCurrentPlaylistItemIndex();
+ method public abstract int getCurrentPosition();
+ method public abstract android.media.MediaPlayer2.DrmInfo getDrmInfo();
+ method public abstract java.lang.String getDrmPropertyString(java.lang.String) throws android.media.MediaPlayer2.NoDrmSchemeException;
+ method public abstract int getDuration();
+ method public abstract android.media.MediaDrm.KeyRequest getKeyRequest(byte[], byte[], java.lang.String, int, java.util.Map<java.lang.String, java.lang.String>) throws android.media.MediaPlayer2.NoDrmSchemeException;
+ method public abstract int getLoopingMode();
+ method public abstract android.os.PersistableBundle getMetrics();
+ method public abstract android.media.PlaybackParams getPlaybackParams();
+ method public abstract java.util.List<android.media.DataSourceDesc> getPlaylist();
+ method public abstract int getSelectedTrack(int);
+ method public abstract android.media.SyncParams getSyncParams();
+ method public abstract android.media.MediaTimestamp getTimestamp();
+ method public abstract java.util.List<android.media.MediaPlayer2.TrackInfo> getTrackInfo();
+ method public abstract int getVideoHeight();
+ method public abstract int getVideoWidth();
+ method public abstract boolean isPlaying();
+ method public abstract void movePlaylistItem(int, int);
+ method public abstract void pause();
+ method public abstract void play();
+ method public abstract void prepareAsync();
+ method public abstract void prepareDrm(java.util.UUID) throws android.media.MediaPlayer2.ProvisioningNetworkErrorException, android.media.MediaPlayer2.ProvisioningServerErrorException, android.media.ResourceBusyException, android.media.UnsupportedSchemeException;
+ method public abstract byte[] provideKeyResponse(byte[], byte[]) throws android.media.DeniedByServerException, android.media.MediaPlayer2.NoDrmSchemeException;
+ method public abstract void registerDrmEventCallback(java.util.concurrent.Executor, android.media.MediaPlayer2.DrmEventCallback);
+ method public abstract void registerEventCallback(java.util.concurrent.Executor, android.media.MediaPlayer2.EventCallback);
+ method public abstract void releaseDrm() throws android.media.MediaPlayer2.NoDrmSchemeException;
+ method public abstract android.media.DataSourceDesc removePlaylistItem(int);
+ method public abstract void reset();
+ method public abstract void restoreKeys(byte[]) throws android.media.MediaPlayer2.NoDrmSchemeException;
+ method public abstract void seekTo(long, int);
+ method public abstract void selectTrack(int);
+ method public abstract void setAudioAttributes(android.media.AudioAttributes);
+ method public abstract void setAudioSessionId(int);
+ method public abstract void setAuxEffectSendLevel(float);
+ method public abstract void setCurrentPlaylistItem(int);
+ method public abstract void setDataSource(android.media.DataSourceDesc) throws java.io.IOException;
+ method public abstract void setDrmPropertyString(java.lang.String, java.lang.String) throws android.media.MediaPlayer2.NoDrmSchemeException;
+ method public abstract void setLoopingMode(int);
+ method public abstract void setNextPlaylistItem(int);
+ method public abstract void setOnDrmConfigHelper(android.media.MediaPlayer2.OnDrmConfigHelper);
+ method public abstract void setPlaybackParams(android.media.PlaybackParams);
+ method public abstract void setPlaylist(java.util.List<android.media.DataSourceDesc>, int) throws java.io.IOException;
+ method public abstract void setSurface(android.view.Surface);
+ method public abstract void setSyncParams(android.media.SyncParams);
+ method public abstract void setVolume(float, float);
+ method public abstract void unregisterDrmEventCallback(android.media.MediaPlayer2.DrmEventCallback);
+ method public abstract void unregisterEventCallback(android.media.MediaPlayer2.EventCallback);
+ field public static final int LOOPING_MODE_FULL = 1; // 0x1
+ field public static final int LOOPING_MODE_NONE = 0; // 0x0
+ field public static final int LOOPING_MODE_SHUFFLE = 3; // 0x3
+ field public static final int LOOPING_MODE_SINGLE = 2; // 0x2
+ field public static final int MEDIA_ERROR_IO = -1004; // 0xfffffc14
+ field public static final int MEDIA_ERROR_MALFORMED = -1007; // 0xfffffc11
+ field public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200; // 0xc8
+ field public static final int MEDIA_ERROR_TIMED_OUT = -110; // 0xffffff92
+ field public static final int MEDIA_ERROR_UNKNOWN = 1; // 0x1
+ field public static final int MEDIA_ERROR_UNSUPPORTED = -1010; // 0xfffffc0e
+ field public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804; // 0x324
+ field public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4; // 0x4
+ field public static final int MEDIA_INFO_BAD_INTERLEAVING = 800; // 0x320
+ field public static final int MEDIA_INFO_BUFFERING_END = 702; // 0x2be
+ field public static final int MEDIA_INFO_BUFFERING_START = 701; // 0x2bd
+ field public static final int MEDIA_INFO_COMPLETE_CALL_PAUSE = 102; // 0x66
+ field public static final int MEDIA_INFO_COMPLETE_CALL_PLAY = 101; // 0x65
+ field public static final int MEDIA_INFO_COMPLETE_CALL_SEEK = 103; // 0x67
+ field public static final int MEDIA_INFO_METADATA_UPDATE = 802; // 0x322
+ field public static final int MEDIA_INFO_NOT_SEEKABLE = 801; // 0x321
+ field public static final int MEDIA_INFO_PLAYBACK_COMPLETE = 5; // 0x5
+ field public static final int MEDIA_INFO_PLAYLIST_END = 6; // 0x6
+ field public static final int MEDIA_INFO_PREPARED = 100; // 0x64
+ field public static final int MEDIA_INFO_STARTED_AS_NEXT = 2; // 0x2
+ field public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902; // 0x386
+ field public static final int MEDIA_INFO_UNKNOWN = 1; // 0x1
+ field public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901; // 0x385
+ field public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805; // 0x325
+ field public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; // 0x3
+ field public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700; // 0x2bc
+ field public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3; // 0x3
+ field public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1; // 0x1
+ field public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2; // 0x2
+ field public static final int PREPARE_DRM_STATUS_SUCCESS = 0; // 0x0
+ field public static final int SEEK_CLOSEST = 3; // 0x3
+ field public static final int SEEK_CLOSEST_SYNC = 2; // 0x2
+ field public static final int SEEK_NEXT_SYNC = 1; // 0x1
+ field public static final int SEEK_PREVIOUS_SYNC = 0; // 0x0
+ field public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; // 0x1
+ }
+
+ public static abstract class MediaPlayer2.DrmEventCallback {
+ ctor public MediaPlayer2.DrmEventCallback();
+ method public void onDrmInfo(android.media.MediaPlayer2, android.media.MediaPlayer2.DrmInfo);
+ method public void onDrmPrepared(android.media.MediaPlayer2, int);
+ }
+
+ public static abstract class MediaPlayer2.DrmInfo {
+ ctor public MediaPlayer2.DrmInfo();
+ method public abstract java.util.Map<java.util.UUID, byte[]> getPssh();
+ method public abstract java.util.List<java.util.UUID> getSupportedSchemes();
+ }
+
+ public static abstract class MediaPlayer2.EventCallback {
+ ctor public MediaPlayer2.EventCallback();
+ method public void onBufferingUpdate(android.media.MediaPlayer2, long, int);
+ method public void onError(android.media.MediaPlayer2, long, int, int);
+ method public void onInfo(android.media.MediaPlayer2, long, int, int);
+ method public void onTimedMetaDataAvailable(android.media.MediaPlayer2, long, android.media.TimedMetaData);
+ method public void onVideoSizeChanged(android.media.MediaPlayer2, long, int, int);
+ }
+
+ public static final class MediaPlayer2.MetricsConstants {
+ field public static final java.lang.String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
+ field public static final java.lang.String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
+ field public static final java.lang.String DURATION = "android.media.mediaplayer.durationMs";
+ field public static final java.lang.String ERRORS = "android.media.mediaplayer.err";
+ field public static final java.lang.String ERROR_CODE = "android.media.mediaplayer.errcode";
+ field public static final java.lang.String FRAMES = "android.media.mediaplayer.frames";
+ field public static final java.lang.String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
+ field public static final java.lang.String HEIGHT = "android.media.mediaplayer.height";
+ field public static final java.lang.String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
+ field public static final java.lang.String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
+ field public static final java.lang.String PLAYING = "android.media.mediaplayer.playingMs";
+ field public static final java.lang.String WIDTH = "android.media.mediaplayer.width";
+ }
+
+ public static abstract class MediaPlayer2.NoDrmSchemeException extends android.media.MediaDrmException {
+ ctor protected MediaPlayer2.NoDrmSchemeException(java.lang.String);
+ }
+
+ public static abstract interface MediaPlayer2.OnDrmConfigHelper {
+ method public abstract void onDrmConfig(android.media.MediaPlayer2);
+ }
+
+ public static abstract class MediaPlayer2.ProvisioningNetworkErrorException extends android.media.MediaDrmException {
+ ctor protected MediaPlayer2.ProvisioningNetworkErrorException(java.lang.String);
+ }
+
+ public static abstract class MediaPlayer2.ProvisioningServerErrorException extends android.media.MediaDrmException {
+ ctor protected MediaPlayer2.ProvisioningServerErrorException(java.lang.String);
+ }
+
+ public static abstract class MediaPlayer2.TrackInfo {
+ ctor public MediaPlayer2.TrackInfo();
+ method public abstract android.media.MediaFormat getFormat();
+ method public abstract java.lang.String getLanguage();
+ method public abstract int getTrackType();
+ method public abstract java.lang.String toString();
+ field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
+ field public static final int MEDIA_TRACK_TYPE_METADATA = 5; // 0x5
+ field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
+ field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
+ }
+
public class MediaRecorder implements android.media.AudioRouting {
ctor public MediaRecorder();
method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
@@ -27383,14 +27657,14 @@
field public java.lang.String providerFriendlyName;
field public long[] roamingConsortiumIds;
field public int status;
- field public java.lang.String[] wepKeys;
- field public int wepTxKeyIndex;
+ field public deprecated java.lang.String[] wepKeys;
+ field public deprecated int wepTxKeyIndex;
}
public static class WifiConfiguration.AuthAlgorithm {
field public static final int LEAP = 2; // 0x2
field public static final int OPEN = 0; // 0x0
- field public static final int SHARED = 1; // 0x1
+ field public static final deprecated int SHARED = 1; // 0x1
field public static final java.lang.String[] strings;
field public static final java.lang.String varName = "auth_alg";
}
@@ -27398,8 +27672,8 @@
public static class WifiConfiguration.GroupCipher {
field public static final int CCMP = 3; // 0x3
field public static final int TKIP = 2; // 0x2
- field public static final int WEP104 = 1; // 0x1
- field public static final int WEP40 = 0; // 0x0
+ field public static final deprecated int WEP104 = 1; // 0x1
+ field public static final deprecated int WEP40 = 0; // 0x0
field public static final java.lang.String[] strings;
field public static final java.lang.String varName = "group";
}
@@ -27408,7 +27682,7 @@
field public static final int IEEE8021X = 3; // 0x3
field public static final int NONE = 0; // 0x0
field public static final int WPA_EAP = 2; // 0x2
- field public static final int WPA_PSK = 1; // 0x1
+ field public static final deprecated int WPA_PSK = 1; // 0x1
field public static final java.lang.String[] strings;
field public static final java.lang.String varName = "key_mgmt";
}
@@ -27416,14 +27690,14 @@
public static class WifiConfiguration.PairwiseCipher {
field public static final int CCMP = 2; // 0x2
field public static final int NONE = 0; // 0x0
- field public static final int TKIP = 1; // 0x1
+ field public static final deprecated int TKIP = 1; // 0x1
field public static final java.lang.String[] strings;
field public static final java.lang.String varName = "pairwise";
}
public static class WifiConfiguration.Protocol {
field public static final int RSN = 1; // 0x1
- field public static final int WPA = 0; // 0x0
+ field public static final deprecated int WPA = 0; // 0x0
field public static final java.lang.String[] strings;
field public static final java.lang.String varName = "proto";
}
@@ -37894,6 +38168,7 @@
method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isRandomizedEncryptionRequired();
method public boolean isStrongBoxBacked();
+ method public boolean isTrustedUserPresenceRequired();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationValidWhileOnBody();
}
@@ -37919,6 +38194,7 @@
method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date);
method public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setTrustedUserPresenceRequired(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int);
@@ -37939,6 +38215,7 @@
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isInsideSecureHardware();
method public boolean isInvalidatedByBiometricEnrollment();
+ method public boolean isTrustedUserPresenceRequired();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
method public boolean isUserAuthenticationValidWhileOnBody();
@@ -38030,7 +38307,6 @@
}
public class StrongBoxUnavailableException extends java.security.ProviderException {
- ctor public StrongBoxUnavailableException();
}
public class UserNotAuthenticatedException extends java.security.InvalidKeyException {
@@ -38039,6 +38315,12 @@
ctor public UserNotAuthenticatedException(java.lang.String, java.lang.Throwable);
}
+ public class UserPresenceUnavailableException extends java.security.InvalidAlgorithmParameterException {
+ ctor public UserPresenceUnavailableException();
+ ctor public UserPresenceUnavailableException(java.lang.String);
+ ctor public UserPresenceUnavailableException(java.lang.String, java.lang.Throwable);
+ }
+
public class WrappedKeyEntry implements java.security.KeyStore.Entry {
ctor public WrappedKeyEntry(byte[], java.lang.String, java.lang.String, java.security.spec.AlgorithmParameterSpec);
method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
@@ -39276,11 +39558,6 @@
field public final int errno;
}
- public class Int32Ref {
- ctor public Int32Ref(int);
- field public int value;
- }
-
public class Int64Ref {
ctor public Int64Ref(long);
field public long value;
@@ -39380,7 +39657,6 @@
method public static int umask(int);
method public static android.system.StructUtsname uname();
method public static void unsetenv(java.lang.String) throws android.system.ErrnoException;
- method public static int waitpid(int, android.system.Int32Ref, int) throws android.system.ErrnoException;
method public static int write(java.io.FileDescriptor, java.nio.ByteBuffer) throws android.system.ErrnoException, java.io.InterruptedIOException;
method public static int write(java.io.FileDescriptor, byte[], int, int) throws android.system.ErrnoException, java.io.InterruptedIOException;
method public static int writev(java.io.FileDescriptor, java.lang.Object[], int[], int[]) throws android.system.ErrnoException, java.io.InterruptedIOException;
@@ -40205,6 +40481,7 @@
method public void onCallEvent(java.lang.String, android.os.Bundle);
method public void onDisconnect();
method public void onExtrasChanged(android.os.Bundle);
+ method public void onHandoverComplete();
method public void onHold();
method public void onPlayDtmfTone(char);
method public void onPostDialContinue(boolean);
@@ -40696,6 +40973,7 @@
field public static final deprecated java.lang.String ACTION_INCOMING_CALL = "android.telecom.action.INCOMING_CALL";
field public static final java.lang.String ACTION_PHONE_ACCOUNT_REGISTERED = "android.telecom.action.PHONE_ACCOUNT_REGISTERED";
field public static final java.lang.String ACTION_PHONE_ACCOUNT_UNREGISTERED = "android.telecom.action.PHONE_ACCOUNT_UNREGISTERED";
+ field public static final java.lang.String ACTION_SHOW_ASSISTED_DIALING_SETTINGS = "android.telecom.action.SHOW_ASSISTED_DIALING_SETTINGS";
field public static final java.lang.String ACTION_SHOW_CALL_ACCESSIBILITY_SETTINGS = "android.telecom.action.SHOW_CALL_ACCESSIBILITY_SETTINGS";
field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS";
field public static final java.lang.String ACTION_SHOW_MISSED_CALLS_NOTIFICATION = "android.telecom.action.SHOW_MISSED_CALLS_NOTIFICATION";
@@ -40792,6 +41070,7 @@
field public static final int EUTRAN = 3; // 0x3
field public static final int GERAN = 1; // 0x1
field public static final int IWLAN = 5; // 0x5
+ field public static final int UNKNOWN = 0; // 0x0
field public static final int UTRAN = 2; // 0x2
}
@@ -40896,6 +41175,8 @@
method public void notifyConfigChangedForSubId(int);
field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
+ field public static final java.lang.String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
+ field public static final java.lang.String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
@@ -41078,6 +41359,7 @@
}
public final class CellIdentityLte extends android.telephony.CellIdentity {
+ method public int getBandwidth();
method public int getCi();
method public int getEarfcn();
method public deprecated int getMcc();
@@ -41121,8 +41403,13 @@
public abstract class CellInfo implements android.os.Parcelable {
method public int describeContents();
+ method public int getCellConnectionStatus();
method public long getTimeStamp();
method public boolean isRegistered();
+ field public static final int CONNECTION_NONE = 0; // 0x0
+ field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1
+ field public static final int CONNECTION_SECONDARY_SERVING = 2; // 0x2
+ field public static final int CONNECTION_UNKNOWN = 2147483647; // 0x7fffffff
field public static final android.os.Parcelable.Creator<android.telephony.CellInfo> CREATOR;
}
@@ -41435,6 +41722,9 @@
ctor public ServiceState(android.os.Parcel);
method protected void copyFrom(android.telephony.ServiceState);
method public int describeContents();
+ method public int[] getCellBandwidths();
+ method public int getChannelNumber();
+ method public int getDuplexMode();
method public boolean getIsManualSelection();
method public int getNetworkId();
method public java.lang.String getOperatorAlphaLong();
@@ -41451,6 +41741,9 @@
method public void setStateOutOfService();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.telephony.ServiceState> CREATOR;
+ field public static final int DUPLEX_MODE_FDD = 1; // 0x1
+ field public static final int DUPLEX_MODE_TDD = 2; // 0x2
+ field public static final int DUPLEX_MODE_UNKNOWN = 0; // 0x0
field public static final int STATE_EMERGENCY_ONLY = 2; // 0x2
field public static final int STATE_IN_SERVICE = 0; // 0x0
field public static final int STATE_OUT_OF_SERVICE = 1; // 0x1
@@ -44673,42 +44966,42 @@
method public void previousMonth();
}
- public final class MutableBoolean {
+ public final deprecated class MutableBoolean {
ctor public MutableBoolean(boolean);
field public boolean value;
}
- public final class MutableByte {
+ public final deprecated class MutableByte {
ctor public MutableByte(byte);
field public byte value;
}
- public final class MutableChar {
+ public final deprecated class MutableChar {
ctor public MutableChar(char);
field public char value;
}
- public final class MutableDouble {
+ public final deprecated class MutableDouble {
ctor public MutableDouble(double);
field public double value;
}
- public final class MutableFloat {
+ public final deprecated class MutableFloat {
ctor public MutableFloat(float);
field public float value;
}
- public final class MutableInt {
+ public final deprecated class MutableInt {
ctor public MutableInt(int);
field public int value;
}
- public final class MutableLong {
+ public final deprecated class MutableLong {
ctor public MutableLong(long);
field public long value;
}
- public final class MutableShort {
+ public final deprecated class MutableShort {
ctor public MutableShort(short);
field public short value;
}
@@ -48483,6 +48776,7 @@
method public java.lang.CharSequence getText();
method public int getTextSelectionEnd();
method public int getTextSelectionStart();
+ method public java.lang.CharSequence getTooltipText();
method public android.view.accessibility.AccessibilityNodeInfo getTraversalAfter();
method public android.view.accessibility.AccessibilityNodeInfo getTraversalBefore();
method public java.lang.String getViewIdResourceName();
@@ -48570,6 +48864,7 @@
method public void setSource(android.view.View, int);
method public void setText(java.lang.CharSequence);
method public void setTextSelection(int, int);
+ method public void setTooltipText(java.lang.CharSequence);
method public void setTraversalAfter(android.view.View);
method public void setTraversalAfter(android.view.View, int);
method public void setTraversalBefore(android.view.View);
@@ -48639,6 +48934,7 @@
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DISMISS;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS;
+ field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_HIDE_TOOLTIP;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_LONG_CLICK;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_MOVE_WINDOW;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY;
@@ -48658,6 +48954,7 @@
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_SELECTION;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_TEXT;
field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_ON_SCREEN;
+ field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_TOOLTIP;
}
public static final class AccessibilityNodeInfo.CollectionInfo {
@@ -49376,6 +49673,7 @@
method public abstract boolean performEditorAction(int);
method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle);
method public abstract boolean reportFullscreenMode(boolean);
+ method public default void reportLanguageHint(android.os.LocaleList);
method public abstract boolean requestCursorUpdates(int);
method public abstract boolean sendKeyEvent(android.view.KeyEvent);
method public abstract boolean setComposingRegion(int, int);
@@ -49617,7 +49915,9 @@
ctor public TextClassification.Options();
method public int describeContents();
method public android.os.LocaleList getDefaultLocales();
+ method public java.util.Calendar getReferenceTime();
method public android.view.textclassifier.TextClassification.Options setDefaultLocales(android.os.LocaleList);
+ method public android.view.textclassifier.TextClassification.Options setReferenceTime(java.util.Calendar);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.view.textclassifier.TextClassification.Options> CREATOR;
}
@@ -49642,7 +49942,10 @@
field public static final int ENTITY_PRESET_NONE = 1; // 0x1
field public static final android.view.textclassifier.TextClassifier NO_OP;
field public static final java.lang.String TYPE_ADDRESS = "address";
+ field public static final java.lang.String TYPE_DATE = "date";
+ field public static final java.lang.String TYPE_DATE_TIME = "datetime";
field public static final java.lang.String TYPE_EMAIL = "email";
+ field public static final java.lang.String TYPE_FLIGHT_NUMBER = "flight";
field public static final java.lang.String TYPE_OTHER = "other";
field public static final java.lang.String TYPE_PHONE = "phone";
field public static final java.lang.String TYPE_UNKNOWN = "";
diff --git a/api/system-current.txt b/api/system-current.txt
index 8678c5e..6be004c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -253,6 +253,7 @@
public class AppOpsManager {
method public static java.lang.String[] getOpStrs();
method public void setUidMode(java.lang.String, int, int);
+ field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
@@ -354,6 +355,20 @@
method public org.json.JSONObject toJson() throws org.json.JSONException;
}
+ public final class StatsManager {
+ method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String);
+ method public byte[] getData(long);
+ method public byte[] getMetadata();
+ method public boolean removeConfiguration(long);
+ method public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent);
+ field public static final java.lang.String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
+ field public static final java.lang.String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
+ field public static final java.lang.String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
+ field public static final java.lang.String EXTRA_STATS_DIMENSIONS_VALUE = "android.app.extra.STATS_DIMENSIONS_VALUE";
+ field public static final java.lang.String EXTRA_STATS_SUBSCRIPTION_ID = "android.app.extra.STATS_SUBSCRIPTION_ID";
+ field public static final java.lang.String EXTRA_STATS_SUBSCRIPTION_RULE_ID = "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
+ }
+
public class VrManager {
method public void setAndBindVrCompositor(android.content.ComponentName);
method public void setPersistentVrModeEnabled(boolean);
@@ -436,7 +451,7 @@
method public java.lang.String[] listAllTransports();
method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver);
method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver, android.app.backup.BackupManagerMonitor, int);
- method public int requestRestore(android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor);
+ method public deprecated int requestRestore(android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor);
method public deprecated java.lang.String selectBackupTransport(java.lang.String);
method public void selectBackupTransport(android.content.ComponentName, android.app.backup.SelectBackupTransportCallback);
method public void setAutoRestore(boolean);
@@ -513,6 +528,7 @@
field public static final int LOG_EVENT_ID_SIGNATURE_MISMATCH = 29; // 0x1d
field public static final int LOG_EVENT_ID_SYSTEM_APP_NO_AGENT = 38; // 0x26
field public static final int LOG_EVENT_ID_TRANSPORT_IS_NULL = 50; // 0x32
+ field public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51; // 0x33
field public static final int LOG_EVENT_ID_UNKNOWN_VERSION = 44; // 0x2c
field public static final int LOG_EVENT_ID_VERSIONS_MATCH = 35; // 0x23
field public static final int LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER = 36; // 0x24
@@ -554,6 +570,7 @@
method public long getCurrentRestoreSet();
method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor);
method public int getRestoreData(android.os.ParcelFileDescriptor);
+ method public int getTransportFlags();
method public int initializeDevice();
method public boolean isAppEligibleForBackup(android.content.pm.PackageInfo, boolean);
method public java.lang.String name();
@@ -569,9 +586,12 @@
method public java.lang.String transportDirName();
field public static final int AGENT_ERROR = -1003; // 0xfffffc15
field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14
+ field public static final int FLAG_INCREMENTAL = 2; // 0x2
+ field public static final int FLAG_NON_INCREMENTAL = 4; // 0x4
field public static final int FLAG_USER_INITIATED = 1; // 0x1
field public static final int NO_MORE_DATA = -1; // 0xffffffff
field public static final int TRANSPORT_ERROR = -1000; // 0xfffffc18
+ field public static final int TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = -1006; // 0xfffffc12
field public static final int TRANSPORT_NOT_INITIALIZED = -1001; // 0xfffffc17
field public static final int TRANSPORT_OK = 0; // 0x0
field public static final int TRANSPORT_PACKAGE_REJECTED = -1002; // 0xfffffc16
@@ -1024,8 +1044,10 @@
package android.content.pm.dex {
public class ArtManager {
- method public boolean isRuntimeProfilingEnabled();
- method public void snapshotRuntimeProfile(java.lang.String, java.lang.String, android.content.pm.dex.ArtManager.SnapshotRuntimeProfileCallback, android.os.Handler);
+ method public boolean isRuntimeProfilingEnabled(int);
+ method public void snapshotRuntimeProfile(int, java.lang.String, java.lang.String, java.util.concurrent.Executor, android.content.pm.dex.ArtManager.SnapshotRuntimeProfileCallback);
+ field public static final int PROFILE_APPS = 0; // 0x0
+ field public static final int PROFILE_BOOT_IMAGE = 1; // 0x1
field public static final int SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND = 1; // 0x1
field public static final int SNAPSHOT_FAILED_INTERNAL_ERROR = 2; // 0x2
field public static final int SNAPSHOT_FAILED_PACKAGE_NOT_FOUND = 0; // 0x0
@@ -2055,21 +2077,21 @@
method public abstract int cancel();
method public abstract void cancelAnnouncement();
method public abstract void close();
- method public abstract int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
+ method public abstract deprecated int getConfiguration(android.hardware.radio.RadioManager.BandConfig[]);
method public android.hardware.radio.ProgramList getDynamicProgramList(android.hardware.radio.ProgramList.Filter);
method public abstract boolean getMute();
method public java.util.Map<java.lang.String, java.lang.String> getParameters(java.util.List<java.lang.String>);
- method public abstract int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
+ method public abstract deprecated int getProgramInformation(android.hardware.radio.RadioManager.ProgramInfo[]);
method public abstract deprecated java.util.List<android.hardware.radio.RadioManager.ProgramInfo> getProgramList(java.util.Map<java.lang.String, java.lang.String>);
method public abstract boolean hasControl();
method public abstract deprecated boolean isAnalogForced();
- method public abstract boolean isAntennaConnected();
+ method public abstract deprecated boolean isAntennaConnected();
method public boolean isConfigFlagSet(int);
method public boolean isConfigFlagSupported(int);
method public abstract int scan(int, boolean);
method public abstract deprecated void setAnalogForced(boolean);
method public void setConfigFlag(int, boolean);
- method public abstract int setConfiguration(android.hardware.radio.RadioManager.BandConfig);
+ method public abstract deprecated int setConfiguration(android.hardware.radio.RadioManager.BandConfig);
method public abstract int setMute(boolean);
method public java.util.Map<java.lang.String, java.lang.String> setParameters(java.util.Map<java.lang.String, java.lang.String>);
method public abstract boolean startBackgroundScan();
@@ -2078,13 +2100,13 @@
method public abstract void tune(android.hardware.radio.ProgramSelector);
field public static final int DIRECTION_DOWN = 1; // 0x1
field public static final int DIRECTION_UP = 0; // 0x0
- field public static final int ERROR_BACKGROUND_SCAN_FAILED = 6; // 0x6
- field public static final int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5; // 0x5
- field public static final int ERROR_CANCELLED = 2; // 0x2
- field public static final int ERROR_CONFIG = 4; // 0x4
- field public static final int ERROR_HARDWARE_FAILURE = 0; // 0x0
- field public static final int ERROR_SCAN_TIMEOUT = 3; // 0x3
- field public static final int ERROR_SERVER_DIED = 1; // 0x1
+ field public static final deprecated int ERROR_BACKGROUND_SCAN_FAILED = 6; // 0x6
+ field public static final deprecated int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5; // 0x5
+ field public static final deprecated int ERROR_CANCELLED = 2; // 0x2
+ field public static final deprecated int ERROR_CONFIG = 4; // 0x4
+ field public static final deprecated int ERROR_HARDWARE_FAILURE = 0; // 0x0
+ field public static final deprecated int ERROR_SCAN_TIMEOUT = 3; // 0x3
+ field public static final deprecated int ERROR_SERVER_DIED = 1; // 0x1
}
public static abstract class RadioTuner.Callback {
@@ -2092,15 +2114,16 @@
method public void onAntennaState(boolean);
method public void onBackgroundScanAvailabilityChange(boolean);
method public void onBackgroundScanComplete();
- method public void onConfigurationChanged(android.hardware.radio.RadioManager.BandConfig);
+ method public deprecated void onConfigurationChanged(android.hardware.radio.RadioManager.BandConfig);
method public void onControlChanged(boolean);
method public void onEmergencyAnnouncement(boolean);
- method public void onError(int);
+ method public deprecated void onError(int);
method public deprecated void onMetadataChanged(android.hardware.radio.RadioMetadata);
method public void onParametersUpdated(java.util.Map<java.lang.String, java.lang.String>);
method public void onProgramInfoChanged(android.hardware.radio.RadioManager.ProgramInfo);
method public void onProgramListChanged();
method public void onTrafficAnnouncement(boolean);
+ method public void onTuneFailed(int, android.hardware.radio.ProgramSelector);
}
}
@@ -2907,9 +2930,23 @@
method public java.lang.String getInterfaceName();
}
+ public final class IpSecTransform implements java.lang.AutoCloseable {
+ method public void startNattKeepalive(android.net.IpSecTransform.NattKeepaliveCallback, int, android.os.Handler) throws java.io.IOException;
+ method public void stopNattKeepalive();
+ }
+
public static class IpSecTransform.Builder {
method public android.net.IpSecTransform buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
- method public android.net.IpSecTransform.Builder setNattKeepalive(int);
+ }
+
+ public static class IpSecTransform.NattKeepaliveCallback {
+ ctor public IpSecTransform.NattKeepaliveCallback();
+ method public void onError(int);
+ method public void onStarted();
+ method public void onStopped();
+ field public static final int ERROR_HARDWARE_ERROR = 3; // 0x3
+ field public static final int ERROR_HARDWARE_UNSUPPORTED = 2; // 0x2
+ field public static final int ERROR_INVALID_NETWORK = 1; // 0x1
}
public class NetworkKey implements android.os.Parcelable {
@@ -3483,6 +3520,125 @@
field public static final java.lang.String ACTION_UPDATE_SMS_SHORT_CODES = "android.intent.action.UPDATE_SMS_SHORT_CODES";
}
+ public class HidlSupport {
+ method public static boolean deepEquals(java.lang.Object, java.lang.Object);
+ method public static int deepHashCode(java.lang.Object);
+ method public static boolean interfacesEqual(android.os.IHwInterface, java.lang.Object);
+ }
+
+ public abstract class HwBinder implements android.os.IHwBinder {
+ method public static final void configureRpcThreadpool(long, boolean);
+ method public static final void joinRpcThreadpool();
+ }
+
+ public class HwBlob {
+ ctor public HwBlob(int);
+ method public final void copyToBoolArray(long, boolean[], int);
+ method public final void copyToDoubleArray(long, double[], int);
+ method public final void copyToFloatArray(long, float[], int);
+ method public final void copyToInt16Array(long, short[], int);
+ method public final void copyToInt32Array(long, int[], int);
+ method public final void copyToInt64Array(long, long[], int);
+ method public final void copyToInt8Array(long, byte[], int);
+ method public final boolean getBool(long);
+ method public final double getDouble(long);
+ method public final float getFloat(long);
+ method public final short getInt16(long);
+ method public final int getInt32(long);
+ method public final long getInt64(long);
+ method public final byte getInt8(long);
+ method public final java.lang.String getString(long);
+ method public final long handle();
+ method public final void putBlob(long, android.os.HwBlob);
+ method public final void putBool(long, boolean);
+ method public final void putBoolArray(long, boolean[]);
+ method public final void putDouble(long, double);
+ method public final void putDoubleArray(long, double[]);
+ method public final void putFloat(long, float);
+ method public final void putFloatArray(long, float[]);
+ method public final void putInt16(long, short);
+ method public final void putInt16Array(long, short[]);
+ method public final void putInt32(long, int);
+ method public final void putInt32Array(long, int[]);
+ method public final void putInt64(long, long);
+ method public final void putInt64Array(long, long[]);
+ method public final void putInt8(long, byte);
+ method public final void putInt8Array(long, byte[]);
+ method public final void putString(long, java.lang.String);
+ method public static java.lang.Boolean[] wrapArray(boolean[]);
+ method public static java.lang.Long[] wrapArray(long[]);
+ method public static java.lang.Byte[] wrapArray(byte[]);
+ method public static java.lang.Short[] wrapArray(short[]);
+ method public static java.lang.Integer[] wrapArray(int[]);
+ method public static java.lang.Float[] wrapArray(float[]);
+ method public static java.lang.Double[] wrapArray(double[]);
+ }
+
+ public class HwParcel {
+ ctor public HwParcel();
+ method public final void enforceInterface(java.lang.String);
+ method public final boolean readBool();
+ method public final java.util.ArrayList<java.lang.Boolean> readBoolVector();
+ method public final android.os.HwBlob readBuffer(long);
+ method public final double readDouble();
+ method public final java.util.ArrayList<java.lang.Double> readDoubleVector();
+ method public final android.os.HwBlob readEmbeddedBuffer(long, long, long, boolean);
+ method public final float readFloat();
+ method public final java.util.ArrayList<java.lang.Float> readFloatVector();
+ method public final short readInt16();
+ method public final java.util.ArrayList<java.lang.Short> readInt16Vector();
+ method public final int readInt32();
+ method public final java.util.ArrayList<java.lang.Integer> readInt32Vector();
+ method public final long readInt64();
+ method public final java.util.ArrayList<java.lang.Long> readInt64Vector();
+ method public final byte readInt8();
+ method public final java.util.ArrayList<java.lang.Byte> readInt8Vector();
+ method public final java.lang.String readString();
+ method public final java.util.ArrayList<java.lang.String> readStringVector();
+ method public final android.os.IHwBinder readStrongBinder();
+ method public final void release();
+ method public final void releaseTemporaryStorage();
+ method public final void send();
+ method public final void verifySuccess();
+ method public final void writeBool(boolean);
+ method public final void writeBoolVector(java.util.ArrayList<java.lang.Boolean>);
+ method public final void writeBuffer(android.os.HwBlob);
+ method public final void writeDouble(double);
+ method public final void writeDoubleVector(java.util.ArrayList<java.lang.Double>);
+ method public final void writeFloat(float);
+ method public final void writeFloatVector(java.util.ArrayList<java.lang.Float>);
+ method public final void writeInt16(short);
+ method public final void writeInt16Vector(java.util.ArrayList<java.lang.Short>);
+ method public final void writeInt32(int);
+ method public final void writeInt32Vector(java.util.ArrayList<java.lang.Integer>);
+ method public final void writeInt64(long);
+ method public final void writeInt64Vector(java.util.ArrayList<java.lang.Long>);
+ method public final void writeInt8(byte);
+ method public final void writeInt8Vector(java.util.ArrayList<java.lang.Byte>);
+ method public final void writeInterfaceToken(java.lang.String);
+ method public final void writeStatus(int);
+ method public final void writeString(java.lang.String);
+ method public final void writeStringVector(java.util.ArrayList<java.lang.String>);
+ method public final void writeStrongBinder(android.os.IHwBinder);
+ field public static final int STATUS_SUCCESS = 0; // 0x0
+ }
+
+ public static abstract class HwParcel.Status implements java.lang.annotation.Annotation {
+ }
+
+ public abstract interface IHwBinder {
+ method public abstract boolean linkToDeath(android.os.IHwBinder.DeathRecipient, long);
+ method public abstract boolean unlinkToDeath(android.os.IHwBinder.DeathRecipient);
+ }
+
+ public static abstract interface IHwBinder.DeathRecipient {
+ method public abstract void serviceDied(long);
+ }
+
+ public abstract interface IHwInterface {
+ method public abstract android.os.IHwBinder asBinder();
+ }
+
public class IncidentManager {
method public void reportIncident(android.os.IncidentReportArgs);
method public void reportIncident(java.lang.String, byte[]);
@@ -3537,6 +3693,27 @@
method public abstract void onResult(android.os.Bundle);
}
+ public final class StatsDimensionsValue implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean getBooleanValue();
+ method public int getField();
+ method public float getFloatValue();
+ method public int getIntValue();
+ method public long getLongValue();
+ method public java.lang.String getStringValue();
+ method public java.util.List<android.os.StatsDimensionsValue> getTupleValueList();
+ method public int getValueType();
+ method public boolean isValueType(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int BOOLEAN_VALUE_TYPE = 5; // 0x5
+ field public static final android.os.Parcelable.Creator<android.os.StatsDimensionsValue> CREATOR;
+ field public static final int FLOAT_VALUE_TYPE = 6; // 0x6
+ field public static final int INT_VALUE_TYPE = 3; // 0x3
+ field public static final int LONG_VALUE_TYPE = 4; // 0x4
+ field public static final int STRING_VALUE_TYPE = 2; // 0x2
+ field public static final int TUPLE_VALUE_TYPE = 7; // 0x7
+ }
+
public class SystemProperties {
method public static java.lang.String get(java.lang.String);
method public static java.lang.String get(java.lang.String, java.lang.String);
@@ -4556,9 +4733,84 @@
field public static final java.lang.String MBMS_STREAMING_SERVICE_ACTION = "android.telephony.action.EmbmsStreaming";
}
+ public class NetworkRegistrationState implements android.os.Parcelable {
+ ctor public NetworkRegistrationState(int, int, int, int, int, boolean, int[], android.telephony.CellIdentity);
+ ctor protected NetworkRegistrationState(android.os.Parcel);
+ method public int describeContents();
+ method public int getAccessNetworkTechnology();
+ method public int[] getAvailableServices();
+ method public int getDomain();
+ method public int getRegState();
+ method public int getTransportType();
+ method public boolean isEmergencyEnabled();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.telephony.NetworkRegistrationState> CREATOR;
+ field public static final int DOMAIN_CS = 1; // 0x1
+ field public static final int DOMAIN_PS = 2; // 0x2
+ field public static final int REG_STATE_DENIED = 3; // 0x3
+ field public static final int REG_STATE_HOME = 1; // 0x1
+ field public static final int REG_STATE_NOT_REG_NOT_SEARCHING = 0; // 0x0
+ field public static final int REG_STATE_NOT_REG_SEARCHING = 2; // 0x2
+ field public static final int REG_STATE_ROAMING = 5; // 0x5
+ field public static final int REG_STATE_UNKNOWN = 4; // 0x4
+ field public static final int SERVICE_TYPE_DATA = 2; // 0x2
+ field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5
+ field public static final int SERVICE_TYPE_SMS = 3; // 0x3
+ field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4
+ field public static final int SERVICE_TYPE_VOICE = 1; // 0x1
+ }
+
+ public abstract class NetworkService extends android.app.Service {
+ method protected abstract android.telephony.NetworkService.NetworkServiceProvider createNetworkServiceProvider(int);
+ field public static final java.lang.String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID";
+ field public static final java.lang.String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService";
+ }
+
+ public class NetworkService.NetworkServiceProvider {
+ ctor public NetworkService.NetworkServiceProvider(int);
+ method public void getNetworkRegistrationState(int, android.telephony.NetworkServiceCallback);
+ method public final int getSlotId();
+ method public final void notifyNetworkRegistrationStateChanged();
+ method protected void onDestroy();
+ }
+
+ public class NetworkServiceCallback {
+ method public void onGetNetworkRegistrationStateComplete(int, android.telephony.NetworkRegistrationState);
+ field public static final int RESULT_ERROR_BUSY = 3; // 0x3
+ field public static final int RESULT_ERROR_FAILED = 5; // 0x5
+ field public static final int RESULT_ERROR_ILLEGAL_STATE = 4; // 0x4
+ field public static final int RESULT_ERROR_INVALID_ARG = 2; // 0x2
+ field public static final int RESULT_ERROR_UNSUPPORTED = 1; // 0x1
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ }
+
+ public class ServiceState implements android.os.Parcelable {
+ method public java.util.List<android.telephony.NetworkRegistrationState> getNetworkRegistrationStates();
+ method public java.util.List<android.telephony.NetworkRegistrationState> getNetworkRegistrationStates(int);
+ method public android.telephony.NetworkRegistrationState getNetworkRegistrationStates(int, int);
+ }
+
public final class SmsManager {
method public void sendMultipartTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
method public void sendTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
+ field public static final int RESULT_CANCELLED = 23; // 0x17
+ field public static final int RESULT_ENCODING_ERROR = 18; // 0x12
+ field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 6; // 0x6
+ field public static final int RESULT_ERROR_NONE = 0; // 0x0
+ field public static final int RESULT_INTERNAL_ERROR = 21; // 0x15
+ field public static final int RESULT_INVALID_ARGUMENTS = 11; // 0xb
+ field public static final int RESULT_INVALID_SMSC_ADDRESS = 19; // 0x13
+ field public static final int RESULT_INVALID_SMS_FORMAT = 14; // 0xe
+ field public static final int RESULT_INVALID_STATE = 12; // 0xc
+ field public static final int RESULT_MODEM_ERROR = 16; // 0x10
+ field public static final int RESULT_NETWORK_ERROR = 17; // 0x11
+ field public static final int RESULT_NETWORK_REJECT = 10; // 0xa
+ field public static final int RESULT_NO_MEMORY = 13; // 0xd
+ field public static final int RESULT_NO_RESOURCES = 22; // 0x16
+ field public static final int RESULT_OPERATION_NOT_ALLOWED = 20; // 0x14
+ field public static final int RESULT_RADIO_NOT_AVAILABLE = 9; // 0x9
+ field public static final int RESULT_REQUEST_NOT_SUPPORTED = 24; // 0x18
+ field public static final int RESULT_SYSTEM_ERROR = 15; // 0xf
}
public class SubscriptionManager {
@@ -4649,6 +4901,7 @@
method public int getSimApplicationState();
method public int getSimCardState();
method public java.util.List<android.telephony.TelephonyHistogram> getTelephonyHistograms();
+ method public android.telephony.UiccSlotInfo[] getUiccSlotsInfo();
method public android.os.Bundle getVisualVoicemailSettings();
method public int getVoiceActivationState();
method public boolean handlePinMmi(java.lang.String);
@@ -4675,6 +4928,7 @@
method public int[] supplyPinReportResult(java.lang.String);
method public boolean supplyPuk(java.lang.String, java.lang.String);
method public int[] supplyPukReportResult(java.lang.String, java.lang.String);
+ method public boolean switchSlots(int[]);
method public void toggleRadioOnOff();
method public void updateServiceLocation();
field public static final java.lang.String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
@@ -4696,6 +4950,25 @@
field public static final int SIM_STATE_PRESENT = 11; // 0xb
}
+ public class UiccSlotInfo implements android.os.Parcelable {
+ ctor public UiccSlotInfo(boolean, boolean, java.lang.String, int);
+ method public int describeContents();
+ method public java.lang.String getCardId();
+ method public int getCardStateInfo();
+ method public boolean getIsActive();
+ method public boolean getIsEuicc();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CARD_STATE_INFO_ABSENT = 1; // 0x1
+ field public static final int CARD_STATE_INFO_ERROR = 3; // 0x3
+ field public static final int CARD_STATE_INFO_PRESENT = 2; // 0x2
+ field public static final int CARD_STATE_INFO_RESTRICTED = 4; // 0x4
+ field public static final android.os.Parcelable.Creator<android.telephony.UiccSlotInfo> CREATOR;
+ field public final java.lang.String cardId;
+ field public final int cardStateInfo;
+ field public final boolean isActive;
+ field public final boolean isEuicc;
+ }
+
public abstract class VisualVoicemailService extends android.app.Service {
method public static final void sendVisualVoicemailSms(android.content.Context, android.telecom.PhoneAccountHandle, java.lang.String, short, java.lang.String, android.app.PendingIntent);
method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings);
@@ -4798,6 +5071,30 @@
}
+package android.telephony.ims.internal.stub {
+
+ public class SmsImplBase {
+ ctor public SmsImplBase();
+ method public void acknowledgeSms(int, int, int);
+ method public void acknowledgeSmsReport(int, int, int);
+ method public java.lang.String getSmsFormat();
+ method public void onReady();
+ method public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException;
+ method public final void onSmsReceived(int, java.lang.String, byte[]) throws java.lang.RuntimeException;
+ method public final void onSmsStatusReportReceived(int, int, java.lang.String, byte[]) throws java.lang.RuntimeException;
+ method public void sendSms(int, int, java.lang.String, java.lang.String, boolean, byte[]);
+ field public static final int DELIVER_STATUS_ERROR = 2; // 0x2
+ field public static final int DELIVER_STATUS_OK = 1; // 0x1
+ field public static final int SEND_STATUS_ERROR = 2; // 0x2
+ field public static final int SEND_STATUS_ERROR_FALLBACK = 4; // 0x4
+ field public static final int SEND_STATUS_ERROR_RETRY = 3; // 0x3
+ field public static final int SEND_STATUS_OK = 1; // 0x1
+ field public static final int STATUS_REPORT_STATUS_ERROR = 2; // 0x2
+ field public static final int STATUS_REPORT_STATUS_OK = 1; // 0x1
+ }
+
+}
+
package android.telephony.mbms {
public final class DownloadRequest implements android.os.Parcelable {
@@ -4899,24 +5196,10 @@
method public int getUid();
}
- public final class StatsManager {
- method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String);
- method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String);
- method public byte[] getData(java.lang.String);
- method public byte[] getData(long);
- method public byte[] getMetadata();
- method public boolean removeConfiguration(java.lang.String);
- method public boolean removeConfiguration(long);
- }
-
}
package android.view {
- public abstract class Window {
- method public void setDisableWallpaperTouchEvents(boolean);
- }
-
public abstract interface WindowManager implements android.view.ViewManager {
method public abstract android.graphics.Region getCurrentImeTouchRegion();
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 254fc15..4e8f904 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -48,6 +48,7 @@
public class AppOpsManager {
method public static java.lang.String[] getOpStrs();
method public void setMode(int, int, java.lang.String, int);
+ field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
@@ -1036,6 +1037,10 @@
package android.widget {
+ public abstract class AbsListView extends android.widget.AdapterView implements android.widget.Filter.FilterListener android.text.TextWatcher android.view.ViewTreeObserver.OnGlobalLayoutListener android.view.ViewTreeObserver.OnTouchModeChangeListener {
+ method public final boolean shouldDrawSelector();
+ }
+
public class CalendarView extends android.widget.FrameLayout {
method public boolean getBoundsForDate(long, android.graphics.Rect);
}
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index a7daa3f..565b092 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -62,6 +62,7 @@
src/storage/StorageManager.cpp \
src/StatsLogProcessor.cpp \
src/StatsService.cpp \
+ src/subscriber/SubscriberReporter.cpp \
src/HashableDimensionKey.cpp \
src/guardrail/MemoryLeakTrackUtil.cpp \
src/guardrail/StatsdStats.cpp
@@ -136,7 +137,7 @@
LOCAL_MODULE_CLASS := EXECUTABLES
-LOCAL_INIT_RC := statsd.rc
+#LOCAL_INIT_RC := statsd.rc
include $(BUILD_EXECUTABLE)
@@ -221,6 +222,44 @@
include $(BUILD_STATIC_JAVA_LIBRARY)
+##############################
+# statsd micro benchmark
+##############################
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := statsd_benchmark
+
+LOCAL_SRC_FILES := $(statsd_common_src) \
+ benchmark/main.cpp \
+ benchmark/hello_world_benchmark.cpp \
+ benchmark/log_event_benchmark.cpp
+
+LOCAL_C_INCLUDES := $(statsd_common_c_includes)
+
+LOCAL_CFLAGS := -Wall \
+ -Werror \
+ -Wno-unused-parameter \
+ -Wno-unused-variable \
+ -Wno-unused-function \
+
+# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
+LOCAL_CFLAGS += -Wno-varargs
+
+LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
+
+LOCAL_STATIC_LIBRARIES := \
+ $(statsd_common_static_libraries)
+
+LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
+ libgtest_prod
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite
+
+LOCAL_MODULE_TAGS := eng tests
+
+include $(BUILD_NATIVE_BENCHMARK)
+
+
statsd_common_src:=
statsd_common_aidl_includes:=
statsd_common_c_includes:=
@@ -228,6 +267,4 @@
statsd_common_shared_libraries:=
-##############################
-
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/cmds/statsd/benchmark/hello_world_benchmark.cpp b/cmds/statsd/benchmark/hello_world_benchmark.cpp
new file mode 100644
index 0000000..c732d39
--- /dev/null
+++ b/cmds/statsd/benchmark/hello_world_benchmark.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#include "benchmark/benchmark.h"
+
+static void BM_StringCreation(benchmark::State& state) {
+ while (state.KeepRunning()) std::string empty_string;
+}
+// Register the function as a benchmark
+BENCHMARK(BM_StringCreation);
+
+// Define another benchmark
+static void BM_StringCopy(benchmark::State& state) {
+ std::string x = "hello";
+ while (state.KeepRunning()) std::string copy(x);
+}
+BENCHMARK(BM_StringCopy);
diff --git a/cmds/statsd/benchmark/log_event_benchmark.cpp b/cmds/statsd/benchmark/log_event_benchmark.cpp
new file mode 100644
index 0000000..43addc2
--- /dev/null
+++ b/cmds/statsd/benchmark/log_event_benchmark.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#include <vector>
+#include "benchmark/benchmark.h"
+#include "logd/LogEvent.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::vector;
+
+/* Special markers for android_log_list_element type */
+static const char EVENT_TYPE_LIST_STOP = '\n'; /* declare end of list */
+static const char EVENT_TYPE_UNKNOWN = '?'; /* protocol error */
+
+static const char EVENT_TYPE_INT = 0;
+static const char EVENT_TYPE_LONG = 1;
+static const char EVENT_TYPE_STRING = 2;
+static const char EVENT_TYPE_LIST = 3;
+static const char EVENT_TYPE_FLOAT = 4;
+
+static const int kLogMsgHeaderSize = 28;
+
+static void write4Bytes(int val, vector<char>* buffer) {
+ buffer->push_back(static_cast<char>(val));
+ buffer->push_back(static_cast<char>((val >> 8) & 0xFF));
+ buffer->push_back(static_cast<char>((val >> 16) & 0xFF));
+ buffer->push_back(static_cast<char>((val >> 24) & 0xFF));
+}
+
+static void getSimpleLogMsgData(log_msg* msg) {
+ vector<char> buffer;
+ // stats_log tag id
+ write4Bytes(1937006964, &buffer);
+ buffer.push_back(EVENT_TYPE_LIST);
+ buffer.push_back(2); // field counts;
+ buffer.push_back(EVENT_TYPE_INT);
+ write4Bytes(10 /* atom id */, &buffer);
+ buffer.push_back(EVENT_TYPE_INT);
+ write4Bytes(99 /* a value to log*/, &buffer);
+ buffer.push_back(EVENT_TYPE_LIST_STOP);
+
+ msg->entry_v1.len = buffer.size();
+ msg->entry.hdr_size = kLogMsgHeaderSize;
+ msg->entry_v1.sec = time(nullptr);
+ std::copy(buffer.begin(), buffer.end(), msg->buf + kLogMsgHeaderSize);
+}
+
+static void BM_LogEventCreation(benchmark::State& state) {
+ log_msg msg;
+ getSimpleLogMsgData(&msg);
+ while (state.KeepRunning()) {
+ benchmark::DoNotOptimize(LogEvent(msg));
+ }
+}
+BENCHMARK(BM_LogEventCreation);
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/benchmark/main.cpp b/cmds/statsd/benchmark/main.cpp
new file mode 100644
index 0000000..08921f3
--- /dev/null
+++ b/cmds/statsd/benchmark/main.cpp
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <benchmark/benchmark.h>
+
+BENCHMARK_MAIN();
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 2526400..edc9f2c 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -137,15 +137,17 @@
StatsdStats::getInstance().noteAtomLogged(
event->GetTagId(), event->GetTimestampNs() / NS_PER_SEC);
- if (mMetricsManagers.empty()) {
- return;
- }
-
// Hard-coded logic to update the isolated uid's in the uid-map.
// The field numbers need to be currently updated by hand with atoms.proto
if (event->GetTagId() == android::util::ISOLATED_UID_CHANGED) {
onIsolatedUidChangedEventLocked(*event);
- } else {
+ }
+
+ if (mMetricsManagers.empty()) {
+ return;
+ }
+
+ if (event->GetTagId() != android::util::ISOLATED_UID_CHANGED) {
// Map the isolated uid to host uid if necessary.
mapIsolatedUidToHostUidIfNecessaryLocked(event);
}
@@ -254,7 +256,7 @@
// Then, check stats-data directory to see there's any file containing
// ConfigMetricsReport from previous shutdowns to concatenate to reports.
- StorageManager::appendConfigMetricsReport(STATS_DATA_DIR, proto);
+ StorageManager::appendConfigMetricsReport(proto);
if (outData != nullptr) {
outData->clear();
@@ -325,8 +327,8 @@
vector<uint8_t> data;
onDumpReportLocked(key, &data);
// TODO: Add a guardrail to prevent accumulation of file on disk.
- string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_DATA_DIR, key.GetUid(),
- (long long)key.GetId(), time(nullptr));
+ string file_name = StringPrintf("%s/%ld_%d_%lld", STATS_DATA_DIR, time(nullptr),
+ key.GetUid(), (long long)key.GetId());
StorageManager::writeFile(file_name.c_str(), &data[0], data.size());
}
}
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index ba628b8..31994e1 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -24,6 +24,7 @@
#include "guardrail/MemoryLeakTrackUtil.h"
#include "guardrail/StatsdStats.h"
#include "storage/StorageManager.h"
+#include "subscriber/SubscriberReporter.h"
#include <android-base/file.h>
#include <binder/IPCThreadState.h>
@@ -67,6 +68,7 @@
void CompanionDeathRecipient::binderDied(const wp<IBinder>& who) {
ALOGW("statscompanion service died");
mAnomalyMonitor->setStatsCompanionService(nullptr);
+ SubscriberReporter::getInstance().setStatsCompanionService(nullptr);
}
// ======================================================================
@@ -380,6 +382,8 @@
"The config can only be set for other UIDs on eng or userdebug "
"builds.\n");
}
+ } else if (argCount == 2 && args[1] == "remove") {
+ good = true;
}
if (!good) {
@@ -681,6 +685,7 @@
VLOG("StatsService::statsCompanionReady linking to statsCompanion.");
IInterface::asBinder(statsCompanion)->linkToDeath(new CompanionDeathRecipient(mAnomalyMonitor));
mAnomalyMonitor->setStatsCompanionService(statsCompanion);
+ SubscriberReporter::getInstance().setStatsCompanionService(statsCompanion);
return Status::ok();
}
@@ -743,7 +748,9 @@
Status StatsService::removeConfiguration(int64_t key, bool* success) {
IPCThreadState* ipc = IPCThreadState::self();
if (checkCallingPermission(String16(kPermissionDump))) {
- mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), key));
+ ConfigKey configKey(ipc->getCallingUid(), key);
+ mConfigManager->RemoveConfig(configKey);
+ SubscriberReporter::getInstance().removeConfig(configKey);
*success = true;
return Status::ok();
} else {
@@ -752,6 +759,42 @@
}
}
+Status StatsService::setBroadcastSubscriber(int64_t configId,
+ int64_t subscriberId,
+ const sp<android::IBinder>& intentSender,
+ bool* success) {
+ VLOG("StatsService::setBroadcastSubscriber called.");
+ IPCThreadState* ipc = IPCThreadState::self();
+ if (checkCallingPermission(String16(kPermissionDump))) {
+ ConfigKey configKey(ipc->getCallingUid(), configId);
+ SubscriberReporter::getInstance()
+ .setBroadcastSubscriber(configKey, subscriberId, intentSender);
+ *success = true;
+ return Status::ok();
+ } else {
+ *success = false;
+ return Status::fromExceptionCode(binder::Status::EX_SECURITY);
+ }
+}
+
+Status StatsService::unsetBroadcastSubscriber(int64_t configId,
+ int64_t subscriberId,
+ bool* success) {
+ VLOG("StatsService::unsetBroadcastSubscriber called.");
+ IPCThreadState* ipc = IPCThreadState::self();
+ if (checkCallingPermission(String16(kPermissionDump))) {
+ ConfigKey configKey(ipc->getCallingUid(), configId);
+ SubscriberReporter::getInstance()
+ .unsetBroadcastSubscriber(configKey, subscriberId);
+ *success = true;
+ return Status::ok();
+ } else {
+ *success = false;
+ return Status::fromExceptionCode(binder::Status::EX_SECURITY);
+ }
+}
+
+
void StatsService::binderDied(const wp <IBinder>& who) {
}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 8d29970..ba6bd24 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -99,6 +99,21 @@
*/
virtual Status removeConfiguration(int64_t key, bool* success) override;
+ /**
+ * Binder call to associate the given config's subscriberId with the given intentSender.
+ * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder).
+ */
+ virtual Status setBroadcastSubscriber(int64_t configId,
+ int64_t subscriberId,
+ const sp<android::IBinder>& intentSender,
+ bool* success) override;
+
+ /**
+ * Binder call to unassociate the given config's subscriberId with any intentSender.
+ */
+ virtual Status unsetBroadcastSubscriber(int64_t configId, int64_t subscriberId,
+ bool* success) override;
+
// TODO: public for testing since statsd doesn't run when system starts. Change to private
// later.
/** Inform statsCompanion that statsd is ready. */
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index e34aed3..ded6c4c 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -21,6 +21,7 @@
#include "external/Perfetto.h"
#include "guardrail/StatsdStats.h"
#include "frameworks/base/libs/incident/proto/android/os/header.pb.h"
+#include "subscriber/SubscriberReporter.h"
#include <android/os/IIncidentManager.h>
#include <android/os/IncidentReportArgs.h>
@@ -233,6 +234,7 @@
}
std::set<int> incidentdSections;
+
for (const Subscription& subscription : mSubscriptions) {
switch (subscription.subscriber_information_case()) {
case Subscription::SubscriberInformationCase::kIncidentdDetails:
@@ -243,6 +245,10 @@
case Subscription::SubscriberInformationCase::kPerfettoDetails:
CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details());
break;
+ case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
+ SubscriberReporter::getInstance()
+ .alertBroadcastSubscriber(mConfigKey, subscription, key);
+ break;
default:
break;
}
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
index de7093d..33e55ab 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -51,10 +51,8 @@
// Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker
// and removes it from firedAlarms.
- // TODO: This will actually be called from a different thread, so make it thread-safe!
- // This means that almost every function in DurationAnomalyTracker needs to be locked.
- // But this should be done at the level of StatsLogProcessor, which needs to lock
- // mMetricsMangers anyway.
+ // Note that this will generally be called from a different thread from the other functions;
+ // the caller is responsible for thread safety.
void informAlarmsFired(const uint64_t& timestampNs,
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override;
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 42994b5..61eeee3 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -101,8 +101,8 @@
}
void ConfigManager::remove_saved_configs(const ConfigKey& key) {
- string prefix = StringPrintf("%d-%lld", key.GetUid(), (long long)key.GetId());
- StorageManager::deletePrefixedFiles(STATS_SERVICE_DIR, prefix.c_str());
+ string suffix = StringPrintf("%d-%lld", key.GetUid(), (long long)key.GetId());
+ StorageManager::deleteSuffixedFiles(STATS_SERVICE_DIR, suffix.c_str());
}
void ConfigManager::RemoveConfigs(int uid) {
@@ -111,6 +111,7 @@
for (auto it = mConfigs.begin(); it != mConfigs.end();) {
// Remove from map
if (it->GetUid() == uid) {
+ remove_saved_configs(*it);
removed.push_back(*it);
mConfigReceivers.erase(*it);
it = mConfigs.erase(it);
@@ -185,8 +186,8 @@
remove_saved_configs(key);
// Then we save the latest config.
- string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_SERVICE_DIR, key.GetUid(),
- (long long)key.GetId(), time(nullptr));
+ string file_name = StringPrintf("%s/%ld_%d_%lld", STATS_SERVICE_DIR, time(nullptr),
+ key.GetUid(), (long long)key.GetId());
const int numBytes = config.ByteSize();
vector<uint8_t> buffer(numBytes);
config.SerializeToArray(&buffer[0], numBytes);
diff --git a/cmds/statsd/src/external/Perfetto.cpp b/cmds/statsd/src/external/Perfetto.cpp
index f7b33e7..1d8a968 100644
--- a/cmds/statsd/src/external/Perfetto.cpp
+++ b/cmds/statsd/src/external/Perfetto.cpp
@@ -20,6 +20,7 @@
#include <android-base/unique_fd.h>
#include <errno.h>
+#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
@@ -65,11 +66,26 @@
// Replace stdin with |readPipe| so the main process can write into it.
if (dup2(readPipe.get(), STDIN_FILENO) < 0) _exit(1);
+ readPipe.reset();
+
+ // Replace stdout/stderr with /dev/null and close any other file
+ // descriptor. This is to avoid SELinux complaining about perfetto
+ // trying to access files accidentally left open by statsd (i.e. files
+ // that have been opened without the O_CLOEXEC flag).
+ int devNullFd = open("/dev/null", O_RDWR | O_CLOEXEC);
+ if (dup2(devNullFd, STDOUT_FILENO) < 0) _exit(2);
+ if (dup2(devNullFd, STDERR_FILENO) < 0) _exit(3);
+ close(devNullFd);
+ for (int i = 0; i < 1024; i++) {
+ if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO) close(i);
+ }
+
execl("/system/bin/perfetto", "perfetto", "--background", "--config", "-", "--dropbox",
kDropboxTag, nullptr);
- // execl() doesn't return in case of success, if we get here something failed.
- _exit(1);
+ // execl() doesn't return in case of success, if we get here something
+ // failed.
+ _exit(4);
}
// Main process.
@@ -93,8 +109,8 @@
return false;
}
- // This does NOT wait for the full duration of the trace. It just waits until the process
- // has read the config from stdin and detached.
+ // This does NOT wait for the full duration of the trace. It just waits until
+ // the process has read the config from stdin and detached.
int childStatus = 0;
waitpid(pid, &childStatus, 0);
if (!WIFEXITED(childStatus) || WEXITSTATUS(childStatus) != 0) {
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 9178daa4..7cb48ea 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -74,6 +74,15 @@
// Default cooldown time for a puller
static const long kDefaultPullerCooldown = 1;
+ // Maximum age (30 days) that files on disk can exist in seconds.
+ static const int kMaxAgeSecond = 60 * 60 * 24 * 30;
+
+ // Maximum number of files (1000) that can be in stats directory on disk.
+ static const int kMaxFileNumber = 1000;
+
+ // Maximum size of all files that can be written to stats directory on disk.
+ static const int kMaxFileSize = 50 * 1024 * 1024;
+
/**
* Report a new config has been received and report the static stats about the config.
*
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 34fa3c4..1ca793c 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -42,6 +42,7 @@
mLogUid = msg.entry_v4.uid;
init(mContext);
if (mContext) {
+ // android_log_destroy will set mContext to NULL
android_log_destroy(&mContext);
}
}
@@ -64,12 +65,17 @@
mContext = create_android_log_parser(buffer, len);
init(mContext);
// destroy the context to save memory.
- android_log_destroy(&mContext);
+ if (mContext) {
+ // android_log_destroy will set mContext to NULL
+ android_log_destroy(&mContext);
+ }
}
}
LogEvent::~LogEvent() {
if (mContext) {
+ // This is for the case when LogEvent is created using the test interface
+ // but init() isn't called.
android_log_destroy(&mContext);
}
}
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 3d6984c..5a4efd4 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -156,7 +156,7 @@
// This field is used when statsD wants to create log event object and write fields to it. After
// calling init() function, this object would be destroyed to save memory usage.
// When the log event is created from log msg, this field is never initiated.
- android_log_context mContext;
+ android_log_context mContext = NULL;
uint64_t mTimestampNs;
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index eefb7dc..91279661 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -281,20 +281,10 @@
void UidMap::clearOutput() {
mOutput.Clear();
- // Re-initialize the initial state for the outputs. This results in extra data being uploaded
- // but helps ensure we can re-construct the UID->app name, versionCode mapping in server.
- auto snapshot = mOutput.add_snapshots();
- for (auto it : mMap) {
- auto t = snapshot->add_package_info();
- t->set_name(it.second.packageName);
- t->set_version(it.second.versionCode);
- t->set_uid(it.first);
- }
-
// Also update the guardrail trackers.
StatsdStats::getInstance().setUidMapChanges(0);
StatsdStats::getInstance().setUidMapSnapshots(1);
- mBytesUsed = snapshot->ByteSize();
+ mBytesUsed = 0;
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
}
@@ -348,6 +338,19 @@
++it_deltas;
}
}
+
+ if (mOutput.snapshots_size() == 0) {
+ // Produce another snapshot. This results in extra data being uploaded but helps
+ // ensure we can re-construct the UID->app name, versionCode mapping in server.
+ auto snapshot = mOutput.add_snapshots();
+ snapshot->set_timestamp_nanos(timestamp);
+ for (auto it : mMap) {
+ auto t = snapshot->add_package_info();
+ t->set_name(it.second.packageName);
+ t->set_version(it.second.versionCode);
+ t->set_uid(it.first);
+ }
+ }
}
mBytesUsed = mOutput.ByteSize(); // Compute actual size after potential deletions.
StatsdStats::getInstance().setCurrentUidMapMemory(mBytesUsed);
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index 1d75e20..83b72d9 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -17,11 +17,15 @@
#define DEBUG true // STOPSHIP if true
#include "Log.h"
-#include "storage/StorageManager.h"
#include "android-base/stringprintf.h"
+#include "guardrail/StatsdStats.h"
+#include "storage/StorageManager.h"
#include <android-base/file.h>
#include <dirent.h>
+#include <private/android_filesystem_config.h>
+#include <fstream>
+#include <iostream>
namespace android {
namespace os {
@@ -31,6 +35,7 @@
using android::util::FIELD_TYPE_MESSAGE;
using std::map;
+#define STATS_DATA_DIR "/data/misc/stats-data"
#define STATS_SERVICE_DIR "/data/misc/stats-service"
// for ConfigMetricsReportList
@@ -39,12 +44,37 @@
using android::base::StringPrintf;
using std::unique_ptr;
+// Returns array of int64_t which contains timestamp in seconds, uid, and
+// configID.
+static void parseFileName(char* name, int64_t* result) {
+ int index = 0;
+ char* substr = strtok(name, "_");
+ while (substr != nullptr && index < 3) {
+ result[index] = StrToInt64(substr);
+ index++;
+ substr = strtok(nullptr, "_");
+ }
+ // When index ends before hitting 3, file name is corrupted. We
+ // intentionally put -1 at index 0 to indicate the error to caller.
+ // TODO: consider removing files with unexpected name format.
+ if (index < 3) {
+ result[0] = -1;
+ }
+}
+
+static string getFilePath(const char* path, int64_t timestamp, int64_t uid, int64_t configID) {
+ return StringPrintf("%s/%lld-%d-%lld", path, (long long)timestamp, (int)uid,
+ (long long)configID);
+}
+
void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) {
int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
if (fd == -1) {
VLOG("Attempt to access %s but failed", file);
return;
}
+ trimToFit(STATS_SERVICE_DIR);
+ trimToFit(STATS_DATA_DIR);
int result = write(fd, buffer, numBytes);
if (result == numBytes) {
@@ -52,6 +82,12 @@
} else {
VLOG("Failed to write %s", file);
}
+
+ result = fchown(fd, AID_STATSD, AID_STATSD);
+ if (result) {
+ VLOG("Failed to chown %s to statsd", file);
+ }
+
close(fd);
}
@@ -78,7 +114,7 @@
}
}
-void StorageManager::deletePrefixedFiles(const char* path, const char* prefix) {
+void StorageManager::deleteSuffixedFiles(const char* path, const char* suffix) {
unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
if (dir == NULL) {
VLOG("Directory does not exist: %s", path);
@@ -88,10 +124,14 @@
dirent* de;
while ((de = readdir(dir.get()))) {
char* name = de->d_name;
- if (name[0] == '.' || strncmp(name, prefix, strlen(prefix)) != 0) {
+ if (name[0] == '.') {
continue;
}
- deleteFile(StringPrintf("%s/%s", path, name).c_str());
+ size_t nameLen = strlen(name);
+ size_t suffixLen = strlen(suffix);
+ if (suffixLen <= nameLen && strncmp(name + nameLen - suffixLen, suffix, suffixLen) == 0) {
+ deleteFile(StringPrintf("%s/%s", path, name).c_str());
+ }
}
}
@@ -109,30 +149,20 @@
if (name[0] == '.') continue;
VLOG("file %s", name);
- int index = 0;
- int uid = 0;
- int64_t configID = 0;
- char* substr = strtok(name, "-");
- // Timestamp lives at index 2 but we skip parsing it as it's not needed.
- while (substr != nullptr && index < 2) {
- if (index == 0) {
- uid = atoi(substr);
- } else if (index == 1) {
- configID = StrToInt64(substr);
- }
- index++;
- substr = strtok(nullptr, "-");
- }
- if (index < 2) continue;
+ int64_t result[3];
+ parseFileName(name, result);
+ if (result[0] == -1) continue;
+ int64_t uid = result[1];
+ int64_t configID = result[2];
- sendBroadcast(ConfigKey(uid, configID));
+ sendBroadcast(ConfigKey((int)uid, configID));
}
}
-void StorageManager::appendConfigMetricsReport(const char* path, ProtoOutputStream& proto) {
- unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
+void StorageManager::appendConfigMetricsReport(ProtoOutputStream& proto) {
+ unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir);
if (dir == NULL) {
- VLOG("Path %s does not exist", path);
+ VLOG("Path %s does not exist", STATS_DATA_DIR);
return;
}
@@ -142,25 +172,13 @@
if (name[0] == '.') continue;
VLOG("file %s", name);
- int index = 0;
- int uid = 0;
- int64_t configID = 0;
- int64_t timestamp = 0;
- char* substr = strtok(name, "-");
- while (substr != nullptr && index < 3) {
- if (index == 0) {
- uid = atoi(substr);
- } else if (index == 1) {
- configID = StrToInt64(substr);
- } else if (index == 2) {
- timestamp = atoi(substr);
- }
- index++;
- substr = strtok(nullptr, "-");
- }
- if (index < 3) continue;
- string file_name = StringPrintf("%s/%d-%lld-%lld", STATS_SERVICE_DIR, uid,
- (long long)configID, (long long)timestamp);
+ int64_t result[3];
+ parseFileName(name, result);
+ if (result[0] == -1) continue;
+ int64_t timestamp = result[0];
+ int64_t uid = result[1];
+ int64_t configID = result[2];
+ string file_name = getFilePath(STATS_DATA_DIR, timestamp, uid, configID);
int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
if (fd != -1) {
string content;
@@ -182,6 +200,7 @@
VLOG("no default config on disk");
return;
}
+ trimToFit(STATS_SERVICE_DIR);
dirent* de;
while ((de = readdir(dir.get()))) {
@@ -189,26 +208,13 @@
if (name[0] == '.') continue;
VLOG("file %s", name);
- int index = 0;
- int uid = 0;
- int64_t configID = 0;
- int64_t timestamp = 0;
- char* substr = strtok(name, "-");
- while (substr != nullptr && index < 3) {
- if (index == 0) {
- uid = atoi(substr);
- } else if (index == 1) {
- configID = StrToInt64(substr);
- } else if (index == 2) {
- timestamp = atoi(substr);
- }
- index++;
- substr = strtok(nullptr, "-");
- }
- if (index < 3) continue;
-
- string file_name = StringPrintf("%s/%d-%lld-%lld", STATS_SERVICE_DIR, uid,
- (long long)configID, (long long)timestamp);
+ int64_t result[3];
+ parseFileName(name, result);
+ if (result[0] == -1) continue;
+ int64_t timestamp = result[0];
+ int64_t uid = result[1];
+ int64_t configID = result[2];
+ string file_name = getFilePath(STATS_SERVICE_DIR, timestamp, uid, configID);
int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
if (fd != -1) {
string content;
@@ -216,7 +222,7 @@
StatsdConfig config;
if (config.ParseFromString(content)) {
configsMap[ConfigKey(uid, configID)] = config;
- VLOG("map key uid=%d|configID=%lld", uid, (long long)configID);
+ VLOG("map key uid=%lld|configID=%lld", (long long)uid, (long long)configID);
}
}
close(fd);
@@ -224,6 +230,67 @@
}
}
+void StorageManager::trimToFit(const char* path) {
+ unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
+ if (dir == NULL) {
+ VLOG("Path %s does not exist", path);
+ return;
+ }
+ dirent* de;
+ int totalFileSize = 0;
+ vector<string> fileNames;
+ while ((de = readdir(dir.get()))) {
+ char* name = de->d_name;
+ if (name[0] == '.') continue;
+
+ int64_t result[3];
+ parseFileName(name, result);
+ if (result[0] == -1) continue;
+ int64_t timestamp = result[0];
+ int64_t uid = result[1];
+ int64_t configID = result[2];
+ string file_name = getFilePath(path, timestamp, uid, configID);
+
+ // Check for timestamp and delete if it's too old.
+ long fileAge = time(nullptr) - timestamp;
+ if (fileAge > StatsdStats::kMaxAgeSecond) {
+ deleteFile(file_name.c_str());
+ }
+
+ fileNames.push_back(file_name);
+ ifstream file(file_name.c_str(), ifstream::in | ifstream::binary);
+ if (file.is_open()) {
+ file.seekg(0, ios::end);
+ int fileSize = file.tellg();
+ file.close();
+ totalFileSize += fileSize;
+ }
+ }
+
+ if (fileNames.size() > StatsdStats::kMaxFileNumber ||
+ totalFileSize > StatsdStats::kMaxFileSize) {
+ // Reverse sort to effectively remove from the back (oldest entries).
+ // This will sort files in reverse-chronological order.
+ sort(fileNames.begin(), fileNames.end(), std::greater<std::string>());
+ }
+
+ // Start removing files from oldest to be under the limit.
+ while (fileNames.size() > 0 && (fileNames.size() > StatsdStats::kMaxFileNumber ||
+ totalFileSize > StatsdStats::kMaxFileSize)) {
+ string file_name = fileNames.at(fileNames.size() - 1);
+ ifstream file(file_name.c_str(), ifstream::in | ifstream::binary);
+ if (file.is_open()) {
+ file.seekg(0, ios::end);
+ int fileSize = file.tellg();
+ file.close();
+ totalFileSize -= fileSize;
+ }
+
+ deleteFile(file_name.c_str());
+ fileNames.pop_back();
+ }
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h
index caf5b8b..d319674 100644
--- a/cmds/statsd/src/storage/StorageManager.h
+++ b/cmds/statsd/src/storage/StorageManager.h
@@ -47,9 +47,9 @@
static void deleteAllFiles(const char* path);
/**
- * Deletes all files whose name matches with a provided prefix.
+ * Deletes all files whose name matches with a provided suffix.
*/
- static void deletePrefixedFiles(const char* path, const char* prefix);
+ static void deleteSuffixedFiles(const char* path, const char* suffix);
/**
* Send broadcasts to relevant receiver for each data stored on disk.
@@ -61,12 +61,18 @@
* Appends ConfigMetricsReport found on disk to the specific proto and
* delete it.
*/
- static void appendConfigMetricsReport(const char* path, ProtoOutputStream& proto);
+ static void appendConfigMetricsReport(ProtoOutputStream& proto);
/**
* Call to load the saved configs from disk.
*/
static void readConfigFromDisk(std::map<ConfigKey, StatsdConfig>& configsMap);
+
+ /**
+ * Trims files in the provided directory to limit the total size, number of
+ * files, accumulation of outdated files.
+ */
+ static void trimToFit(const char* dir);
};
} // namespace statsd
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
new file mode 100644
index 0000000..f912e4b
--- /dev/null
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG false // STOPSHIP if true
+#include "Log.h"
+
+#include "SubscriberReporter.h"
+
+using android::IBinder;
+using std::lock_guard;
+using std::unordered_map;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey,
+ int64_t subscriberId,
+ const sp<IBinder>& intentSender) {
+ VLOG("SubscriberReporter::setBroadcastSubscriber called.");
+ lock_guard<std::mutex> lock(mLock);
+ mIntentMap[configKey][subscriberId] = intentSender;
+}
+
+void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey,
+ int64_t subscriberId) {
+ VLOG("SubscriberReporter::unsetBroadcastSubscriber called.");
+ lock_guard<std::mutex> lock(mLock);
+ auto subscriberMapIt = mIntentMap.find(configKey);
+ if (subscriberMapIt != mIntentMap.end()) {
+ subscriberMapIt->second.erase(subscriberId);
+ if (subscriberMapIt->second.empty()) {
+ mIntentMap.erase(configKey);
+ }
+ }
+}
+
+void SubscriberReporter::removeConfig(const ConfigKey& configKey) {
+ VLOG("SubscriberReporter::removeConfig called.");
+ lock_guard<std::mutex> lock(mLock);
+ mIntentMap.erase(configKey);
+}
+
+void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey,
+ const Subscription& subscription,
+ const HashableDimensionKey& dimKey) const {
+ // Reminder about ids:
+ // subscription id - name of the Subscription (that ties the Alert to the broadcast)
+ // subscription rule_id - the name of the Alert (that triggers the broadcast)
+ // subscriber_id - name of the PendingIntent to use to send the broadcast
+ // config uid - the uid that uploaded the config (and therefore gave the PendingIntent,
+ // although the intent may be to broadcast to a different uid)
+ // config id - the name of this config (for this particular uid)
+
+ VLOG("SubscriberReporter::alertBroadcastSubscriber called.");
+ lock_guard<std::mutex> lock(mLock);
+
+ if (!subscription.has_broadcast_subscriber_details()
+ || !subscription.broadcast_subscriber_details().has_subscriber_id()) {
+ ALOGE("Broadcast subscriber does not have an id.");
+ return;
+ }
+ int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id();
+
+ auto it1 = mIntentMap.find(configKey);
+ if (it1 == mIntentMap.end()) {
+ ALOGW("Cannot inform subscriber for missing config key %s ", configKey.ToString().c_str());
+ return;
+ }
+ auto it2 = it1->second.find(subscriberId);
+ if (it2 == it1->second.end()) {
+ ALOGW("Cannot inform subscriber of config %s for missing subscriberId %lld ",
+ configKey.ToString().c_str(), (long long)subscriberId);
+ return;
+ }
+ sendBroadcastLocked(it2->second, configKey, subscription, dimKey);
+}
+
+void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender,
+ const ConfigKey& configKey,
+ const Subscription& subscription,
+ const HashableDimensionKey& dimKey) const {
+ VLOG("SubscriberReporter::sendBroadcastLocked called.");
+ if (mStatsCompanionService == nullptr) {
+ ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService.");
+ return;
+ }
+ mStatsCompanionService->sendSubscriberBroadcast(intentSender,
+ configKey.GetUid(),
+ configKey.GetId(),
+ subscription.id(),
+ subscription.rule_id(),
+ protoToStatsDimensionsValue(dimKey));
+}
+
+StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue(
+ const HashableDimensionKey& dimKey) {
+ return protoToStatsDimensionsValue(dimKey.getDimensionsValue());
+}
+
+StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue(
+ const DimensionsValue& protoDimsVal) {
+ int32_t field = protoDimsVal.field();
+
+ switch (protoDimsVal.value_case()) {
+ case DimensionsValue::ValueCase::kValueStr:
+ return StatsDimensionsValue(field, String16(protoDimsVal.value_str().c_str()));
+ case DimensionsValue::ValueCase::kValueInt:
+ return StatsDimensionsValue(field, static_cast<int32_t>(protoDimsVal.value_int()));
+ case DimensionsValue::ValueCase::kValueLong:
+ return StatsDimensionsValue(field, static_cast<int64_t>(protoDimsVal.value_long()));
+ case DimensionsValue::ValueCase::kValueBool:
+ return StatsDimensionsValue(field, static_cast<bool>(protoDimsVal.value_bool()));
+ case DimensionsValue::ValueCase::kValueFloat:
+ return StatsDimensionsValue(field, static_cast<float>(protoDimsVal.value_float()));
+ case DimensionsValue::ValueCase::kValueTuple:
+ {
+ int sz = protoDimsVal.value_tuple().dimensions_value_size();
+ std::vector<StatsDimensionsValue> sdvVec(sz);
+ for (int i = 0; i < sz; i++) {
+ sdvVec[i] = protoToStatsDimensionsValue(
+ protoDimsVal.value_tuple().dimensions_value(i));
+ }
+ return StatsDimensionsValue(field, sdvVec);
+ }
+ default:
+ ALOGW("protoToStatsDimensionsValue failed: illegal type.");
+ return StatsDimensionsValue();
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
new file mode 100644
index 0000000..5bb458a
--- /dev/null
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+#include <android/os/IStatsCompanionService.h>
+#include <utils/RefBase.h>
+
+#include "config/ConfigKey.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // subscription
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" // DimensionsValue
+#include "android/os/StatsDimensionsValue.h"
+#include "HashableDimensionKey.h"
+
+#include <mutex>
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Reports information to subscribers.
+// Single instance shared across the process. All methods are thread safe.
+class SubscriberReporter {
+public:
+ /** Get (singleton) instance of SubscriberReporter. */
+ static SubscriberReporter& getInstance() {
+ static SubscriberReporter subscriberReporter;
+ return subscriberReporter;
+ }
+
+ ~SubscriberReporter(){};
+ SubscriberReporter(SubscriberReporter const&) = delete;
+ void operator=(SubscriberReporter const&) = delete;
+
+ /**
+ * Tells SubscriberReporter what IStatsCompanionService to use.
+ * May be nullptr, but SubscriberReporter will not send broadcasts for any calls
+ * to alertBroadcastSubscriber that occur while nullptr.
+ */
+ void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
+ std::lock_guard<std::mutex> lock(mLock);
+ sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
+ mStatsCompanionService = statsCompanionService;
+ }
+
+ /**
+ * Stores the given intentSender, associating it with the given (configKey, subscriberId) pair.
+ * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder).
+ */
+ void setBroadcastSubscriber(const ConfigKey& configKey,
+ int64_t subscriberId,
+ const sp<android::IBinder>& intentSender);
+
+ /**
+ * Erases any intentSender information from the given (configKey, subscriberId) pair.
+ */
+ void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId);
+
+ /** Remove all information stored by SubscriberReporter about the given config. */
+ void removeConfig(const ConfigKey& configKey);
+
+ /**
+ * Sends a broadcast via the intentSender previously stored for the
+ * given (configKey, subscriberId) pair by setBroadcastSubscriber.
+ * Information about the subscriber, as well as information extracted from the dimKey, is sent.
+ */
+ void alertBroadcastSubscriber(const ConfigKey& configKey,
+ const Subscription& subscription,
+ const HashableDimensionKey& dimKey) const;
+
+private:
+ SubscriberReporter() {};
+
+ mutable std::mutex mLock;
+
+ /** Binder interface for communicating with StatsCompanionService. */
+ sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+
+ /** Maps <ConfigKey, SubscriberId> -> IBinder (which represents an IIntentSender). */
+ std::unordered_map<ConfigKey,
+ std::unordered_map<int64_t, sp<android::IBinder>>> mIntentMap;
+
+ /**
+ * Sends a broadcast via the given intentSender (using mStatsCompanionService), along
+ * with the information in the other parameters.
+ */
+ void sendBroadcastLocked(const sp<android::IBinder>& intentSender,
+ const ConfigKey& configKey,
+ const Subscription& subscription,
+ const HashableDimensionKey& dimKey) const;
+
+ /** Converts a stats_log.proto DimensionsValue to a StatsDimensionsValue. */
+ static StatsDimensionsValue protoToStatsDimensionsValue(
+ const DimensionsValue& protoDimsVal);
+
+ /** Converts a HashableDimensionKey to a StatsDimensionsValue. */
+ static StatsDimensionsValue protoToStatsDimensionsValue(
+ const HashableDimensionKey& dimKey);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 5292f24..f26c10d 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -178,16 +178,16 @@
EXPECT_EQ(1, results.snapshots_size());
// It should be cleared now
- EXPECT_EQ(0, m.mOutput.snapshots_size());
+ EXPECT_EQ(1, m.mOutput.snapshots_size());
results = m.getOutput(3, config1);
- EXPECT_EQ(0, results.snapshots_size());
+ EXPECT_EQ(1, results.snapshots_size());
// Now add another configuration.
m.OnConfigUpdated(config2);
m.updateApp(5, String16(kApp1.c_str()), 1000, 40);
EXPECT_EQ(1, m.mOutput.changes_size());
results = m.getOutput(6, config1);
- EXPECT_EQ(0, results.snapshots_size());
+ EXPECT_EQ(1, results.snapshots_size());
EXPECT_EQ(1, results.changes_size());
EXPECT_EQ(1, m.mOutput.changes_size());
@@ -197,15 +197,15 @@
// We still can't remove anything.
results = m.getOutput(8, config1);
- EXPECT_EQ(0, results.snapshots_size());
+ EXPECT_EQ(1, results.snapshots_size());
EXPECT_EQ(2, results.changes_size());
EXPECT_EQ(2, m.mOutput.changes_size());
results = m.getOutput(9, config2);
- EXPECT_EQ(0, results.snapshots_size());
+ EXPECT_EQ(1, results.snapshots_size());
EXPECT_EQ(2, results.changes_size());
// At this point both should be cleared.
- EXPECT_EQ(0, m.mOutput.snapshots_size());
+ EXPECT_EQ(1, m.mOutput.snapshots_size());
EXPECT_EQ(0, m.mOutput.changes_size());
}
@@ -228,10 +228,8 @@
m.updateApp(3, String16(kApp1.c_str()), 1000, 40);
EXPECT_TRUE(m.mBytesUsed > snapshot_bytes);
- size_t bytesWithSnapshotChange = m.mBytesUsed;
m.getOutput(2, config1);
- EXPECT_TRUE(m.mBytesUsed < bytesWithSnapshotChange);
size_t prevBytes = m.mBytesUsed;
m.getOutput(4, config1);
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
index 4f9032f..d39aa1d 100644
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
@@ -16,12 +16,12 @@
package com.android.statsd.dogfood;
import android.app.Activity;
+import android.app.StatsManager;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.util.StatsLog;
-import android.util.StatsManager;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
index 26c1c72..652f6b2 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
+import android.app.StatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -34,7 +35,6 @@
import android.text.TextWatcher;
import android.util.Log;
import android.util.StatsLog;
-import android.util.StatsManager;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.view.MotionEvent;
diff --git a/config/hiddenapi-blacklist.txt b/config/hiddenapi-blacklist.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/config/hiddenapi-blacklist.txt
diff --git a/config/hiddenapi-dark-greylist.txt b/config/hiddenapi-dark-greylist.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/config/hiddenapi-dark-greylist.txt
diff --git a/core/java/Android.bp b/core/java/Android.bp
index afa08e6..f7c5c57 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -11,7 +11,8 @@
// only used by key_store_service
cc_library_shared {
name: "libkeystore_aidl",
- srcs: ["android/security/IKeystoreService.aidl"],
+ srcs: ["android/security/IKeystoreService.aidl",
+ "android/security/IConfirmationPromptCallback.aidl"],
aidl: {
export_aidl_headers: true,
include_dirs: [
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b5a9412..fcf8bd7 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -28,7 +28,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.UriPermission;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.ConfigurationInfo;
@@ -181,7 +180,8 @@
BUGREPORT_OPTION_INTERACTIVE,
BUGREPORT_OPTION_REMOTE,
BUGREPORT_OPTION_WEAR,
- BUGREPORT_OPTION_TELEPHONY
+ BUGREPORT_OPTION_TELEPHONY,
+ BUGREPORT_OPTION_WIFI
})
public @interface BugreportMode {}
/**
@@ -216,6 +216,12 @@
public static final int BUGREPORT_OPTION_TELEPHONY = 4;
/**
+ * Takes a lightweight bugreport that only includes a few sections related to Wifi.
+ * @hide
+ */
+ public static final int BUGREPORT_OPTION_WIFI = 5;
+
+ /**
* <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code
* <meta-data>}</a> name for a 'home' Activity that declares a package that is to be
* uninstalled in lieu of the declaring one. The package named here must be
@@ -2679,17 +2685,22 @@
/**
* Permits an application to get the persistent URI permissions granted to another.
*
- * <p>Typically called by Settings.
+ * <p>Typically called by Settings or DocumentsUI, requires
+ * {@code GET_APP_GRANTED_URI_PERMISSIONS}.
*
- * @param packageName application to look for the granted permissions
+ * @param packageName application to look for the granted permissions, or {@code null} to get
+ * granted permissions for all applications
* @return list of granted URI permissions
*
* @hide
*/
- public ParceledListSlice<UriPermission> getGrantedUriPermissions(String packageName) {
+ public ParceledListSlice<GrantedUriPermission> getGrantedUriPermissions(
+ @Nullable String packageName) {
try {
- return getService().getGrantedUriPermissions(packageName,
- UserHandle.myUserId());
+ @SuppressWarnings("unchecked")
+ final ParceledListSlice<GrantedUriPermission> castedList = getService()
+ .getGrantedUriPermissions(packageName, UserHandle.myUserId());
+ return castedList;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2698,7 +2709,7 @@
/**
* Permits an application to clear the persistent URI permissions granted to another.
*
- * <p>Typically called by Settings.
+ * <p>Typically called by Settings, requires {@code CLEAR_APP_GRANTED_URI_PERMISSIONS}.
*
* @param packageName application to clear its granted permissions
*
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index ac6cba1..5d0143a 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.NonNull;
+import android.app.ActivityManager.StackInfo;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
@@ -38,6 +39,8 @@
import dalvik.system.CloseGuard;
+import java.util.List;
+
/**
* Activity container that allows launching activities into itself and does input forwarding.
* <p>Creation of this view is only allowed to callers who have
@@ -58,10 +61,13 @@
private final SurfaceCallback mSurfaceCallback;
private StateCallback mActivityViewCallback;
+ private IActivityManager mActivityManager;
private IInputForwarder mInputForwarder;
// Temp container to store view coordinates on screen.
private final int[] mLocationOnScreen = new int[2];
+ private TaskStackListener mTaskStackListener;
+
private final CloseGuard mGuard = CloseGuard.get();
private boolean mOpened; // Protected by mGuard.
@@ -76,6 +82,7 @@
public ActivityView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ mActivityManager = ActivityManager.getService();
mSurfaceView = new SurfaceView(context);
mSurfaceCallback = new SurfaceCallback();
mSurfaceView.getHolder().addCallback(mSurfaceCallback);
@@ -303,6 +310,12 @@
mInputForwarder = InputManager.getInstance().createInputForwarder(
mVirtualDisplay.getDisplay().getDisplayId());
+ mTaskStackListener = new TaskBackgroundChangeListener();
+ try {
+ mActivityManager.registerTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register task stack listener", e);
+ }
}
private void performRelease() {
@@ -317,6 +330,15 @@
}
cleanTapExcludeRegion();
+ if (mTaskStackListener != null) {
+ try {
+ mActivityManager.unregisterTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unregister task stack listener", e);
+ }
+ mTaskStackListener = null;
+ }
+
final boolean displayReleased;
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
@@ -369,4 +391,42 @@
super.finalize();
}
}
+
+ /**
+ * A task change listener that detects background color change of the topmost stack on our
+ * virtual display and updates the background of the surface view. This background will be shown
+ * when surface view is resized, but the app hasn't drawn its content in new size yet.
+ */
+ private class TaskBackgroundChangeListener extends TaskStackListener {
+
+ @Override
+ public void onTaskDescriptionChanged(int taskId, ActivityManager.TaskDescription td)
+ throws RemoteException {
+ if (mVirtualDisplay == null) {
+ return;
+ }
+
+ // Find the topmost task on our virtual display - it will define the background
+ // color of the surface view during resizing.
+ final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
+ final List<StackInfo> stackInfoList = mActivityManager.getAllStackInfos();
+
+ // Iterate through stacks from top to bottom.
+ final int stackCount = stackInfoList.size();
+ for (int i = 0; i < stackCount; i++) {
+ final StackInfo stackInfo = stackInfoList.get(i);
+ // Only look for stacks on our virtual display.
+ if (stackInfo.displayId != displayId) {
+ continue;
+ }
+ // Found the topmost stack on target display. Now check if the topmost task's
+ // description changed.
+ if (taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+ mSurfaceView.setResizeBackgroundColor(td.getBackgroundColor());
+ }
+ break;
+ }
+ }
+ }
+
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7ca6802..e923fb2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -263,8 +263,10 @@
public static final int OP_REQUEST_DELETE_PACKAGES = 72;
/** @hide Bind an accessibility service. */
public static final int OP_BIND_ACCESSIBILITY_SERVICE = 73;
+ /** @hide Continue handover of a call from another app */
+ public static final int OP_ACCEPT_HANDOVER = 74;
/** @hide */
- public static final int _NUM_OP = 74;
+ public static final int _NUM_OP = 75;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -378,7 +380,13 @@
/** Answer incoming phone calls */
public static final String OPSTR_ANSWER_PHONE_CALLS
= "android:answer_phone_calls";
-
+ /**
+ * Accept call handover
+ * @hide
+ */
+ @SystemApi @TestApi
+ public static final String OPSTR_ACCEPT_HANDOVER
+ = "android:accept_handover";
/** @hide */
@SystemApi @TestApi
public static final String OPSTR_GPS = "android:gps";
@@ -528,6 +536,7 @@
OP_USE_SIP,
OP_PROCESS_OUTGOING_CALLS,
OP_ANSWER_PHONE_CALLS,
+ OP_ACCEPT_HANDOVER,
// Microphone
OP_RECORD_AUDIO,
// Camera
@@ -626,6 +635,7 @@
OP_CHANGE_WIFI_STATE,
OP_REQUEST_DELETE_PACKAGES,
OP_BIND_ACCESSIBILITY_SERVICE,
+ OP_ACCEPT_HANDOVER,
};
/**
@@ -706,6 +716,7 @@
OPSTR_CHANGE_WIFI_STATE,
OPSTR_REQUEST_DELETE_PACKAGES,
OPSTR_BIND_ACCESSIBILITY_SERVICE,
+ OPSTR_ACCEPT_HANDOVER,
};
/**
@@ -787,6 +798,7 @@
"CHANGE_WIFI_STATE",
"REQUEST_DELETE_PACKAGES",
"BIND_ACCESSIBILITY_SERVICE",
+ "ACCEPT_HANDOVER",
};
/**
@@ -868,6 +880,7 @@
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.REQUEST_DELETE_PACKAGES,
Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
+ Manifest.permission.ACCEPT_HANDOVER,
};
/**
@@ -950,6 +963,7 @@
null, // OP_CHANGE_WIFI_STATE
null, // REQUEST_DELETE_PACKAGES
null, // OP_BIND_ACCESSIBILITY_SERVICE
+ null, // ACCEPT_HANDOVER
};
/**
@@ -1031,6 +1045,7 @@
false, // OP_CHANGE_WIFI_STATE
false, // OP_REQUEST_DELETE_PACKAGES
false, // OP_BIND_ACCESSIBILITY_SERVICE
+ false, // ACCEPT_HANDOVER
};
/**
@@ -1111,6 +1126,7 @@
AppOpsManager.MODE_ALLOWED, // OP_CHANGE_WIFI_STATE
AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES
AppOpsManager.MODE_ALLOWED, // OP_BIND_ACCESSIBILITY_SERVICE
+ AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER
};
/**
@@ -1195,6 +1211,7 @@
false, // OP_CHANGE_WIFI_STATE
false, // OP_REQUEST_DELETE_PACKAGES
false, // OP_BIND_ACCESSIBILITY_SERVICE
+ false, // ACCEPT_HANDOVER
};
/**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4914ffa..ea94042 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -913,14 +913,14 @@
/** @hide */
@Override
- public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+ public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
throw new AndroidRuntimeException(
"Calling startActivities() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
+ " Is this really what you want?");
}
- mMainThread.getInstrumentation().execStartActivitiesAsUser(
+ return mMainThread.getInstrumentation().execStartActivitiesAsUser(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intents, options, userHandle.getIdentifier());
}
diff --git a/core/java/android/app/GrantedUriPermission.aidl b/core/java/android/app/GrantedUriPermission.aidl
new file mode 100644
index 0000000..2734af0
--- /dev/null
+++ b/core/java/android/app/GrantedUriPermission.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2018 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.app;
+
+/** @hide */
+parcelable GrantedUriPermission;
\ No newline at end of file
diff --git a/core/java/android/app/GrantedUriPermission.java b/core/java/android/app/GrantedUriPermission.java
new file mode 100644
index 0000000..9e84fe1
--- /dev/null
+++ b/core/java/android/app/GrantedUriPermission.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.UriPermission;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents an {@link UriPermission} granted to a package.
+ *
+ * {@hide}
+ */
+public class GrantedUriPermission implements Parcelable {
+
+ public final Uri uri;
+ public final String packageName;
+
+ public GrantedUriPermission(@NonNull Uri uri, @Nullable String packageName) {
+ this.uri = uri;
+ this.packageName = packageName;
+ }
+
+ @Override
+ public String toString() {
+ return packageName + ":" + uri;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(uri, flags);
+ out.writeString(packageName);
+ }
+
+ public static final Parcelable.Creator<GrantedUriPermission> CREATOR =
+ new Parcelable.Creator<GrantedUriPermission>() {
+ @Override
+ public GrantedUriPermission createFromParcel(Parcel in) {
+ return new GrantedUriPermission(in);
+ }
+
+ @Override
+ public GrantedUriPermission[] newArray(int size) {
+ return new GrantedUriPermission[size];
+ }
+ };
+
+ private GrantedUriPermission(Parcel in) {
+ uri = in.readParcelable(null);
+ packageName = in.readString();
+ }
+}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 5f5d834..5382e66 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -19,6 +19,7 @@
import android.app.ActivityManager;
import android.app.ApplicationErrorReport;
import android.app.ContentProviderHolder;
+import android.app.GrantedUriPermission;
import android.app.IApplicationThread;
import android.app.IActivityController;
import android.app.IAppTask;
@@ -357,6 +358,20 @@
*/
void requestTelephonyBugReport(in String shareTitle, in String shareDescription);
+ /**
+ * Deprecated - This method is only used by Wifi, and it will soon be replaced by a proper
+ * bug report API.
+ *
+ * Takes a minimal bugreport of Wifi-related state.
+ *
+ * @param shareTitle should be a valid legible string less than 50 chars long
+ * @param shareDescription should be less than 91 bytes when encoded into UTF-8 format
+ *
+ * @throws IllegalArgumentException if shareTitle or shareDescription is too big or if the
+ * parameters cannot be encoding to an UTF-8 charset.
+ */
+ void requestWifiBugReport(in String shareTitle, in String shareDescription);
+
long inputDispatchingTimedOut(int pid, boolean aboveSystem, in String reason);
void clearPendingBackup();
Intent getIntentForIntentSender(in IIntentSender sender);
@@ -555,7 +570,7 @@
in Rect tempDockedTaskInsetBounds,
in Rect tempOtherTaskBounds, in Rect tempOtherTaskInsetBounds);
int setVrMode(in IBinder token, boolean enabled, in ComponentName packageName);
- // Gets the URI permissions granted to an arbitrary package.
+ // Gets the URI permissions granted to an arbitrary package (or all packages if null)
// NOTE: this is different from getPersistedUriPermissions(), which returns the URIs the package
// granted to another packages (instead of those granted to it).
ParceledListSlice getGrantedUriPermissions(in String packageName, int userId);
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 4a85efd..3aeef14 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -49,11 +49,13 @@
*
* @param callbackBinder Binder on which to indicate operation completion,
* passed here as a convenience to the agent.
+ *
+ * @param transportFlags Flags with additional information about the transport.
*/
void doBackup(in ParcelFileDescriptor oldState,
in ParcelFileDescriptor data,
in ParcelFileDescriptor newState,
- long quotaBytes, int token, IBackupManager callbackBinder);
+ long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags);
/**
* Restore an entire data snapshot to the application.
@@ -100,13 +102,17 @@
*
* @param callbackBinder Binder on which to indicate operation completion,
* passed here as a convenience to the agent.
+ *
+ * @param transportFlags Flags with additional information about transport.
*/
- void doFullBackup(in ParcelFileDescriptor data, long quotaBytes, int token, IBackupManager callbackBinder);
+ void doFullBackup(in ParcelFileDescriptor data, long quotaBytes, int token,
+ IBackupManager callbackBinder, int transportFlags);
/**
* Estimate how much data a full backup will deliver
*/
- void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder);
+ void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder,
+ int transportFlags);
/**
* Tells the application agent that the backup data size exceeded current transport quota.
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 3c38a4e..f90b276 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1688,9 +1688,13 @@
* {@link ActivityMonitor} objects only match against the first activity in
* the array.
*
+ * @return The corresponding flag {@link ActivityManager#START_CANCELED},
+ * {@link ActivityManager#START_SUCCESS} etc. indicating whether the launch was
+ * successful.
+ *
* {@hide}
*/
- public void execStartActivitiesAsUser(Context who, IBinder contextThread,
+ public int execStartActivitiesAsUser(Context who, IBinder contextThread,
IBinder token, Activity target, Intent[] intents, Bundle options,
int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
@@ -1705,11 +1709,11 @@
}
if (result != null) {
am.mHits++;
- return;
+ return ActivityManager.START_CANCELED;
} else if (am.match(who, null, intents[0])) {
am.mHits++;
if (am.isBlocking()) {
- return;
+ return ActivityManager.START_CANCELED;
}
break;
}
@@ -1727,6 +1731,7 @@
.startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes,
token, options, userId);
checkStartActivityResult(result, intents[0]);
+ return result;
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 26f4980..d24d4f3 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -28,6 +28,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.dex.ArtManager;
import android.content.pm.split.SplitDependencyLoader;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
@@ -35,7 +36,6 @@
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
-import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
@@ -49,13 +49,15 @@
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.LogPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAdjustments;
+
import com.android.internal.util.ArrayUtils;
+
import dalvik.system.VMRuntime;
+
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -749,13 +751,6 @@
}
}
- // Keep in sync with installd (frameworks/native/cmds/installd/commands.cpp).
- private static File getPrimaryProfileFile(String packageName) {
- File profileDir = Environment.getDataProfilesDePackageDirectory(
- UserHandle.myUserId(), packageName);
- return new File(profileDir, "primary.prof");
- }
-
private void setupJitProfileSupport() {
if (!SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) {
return;
@@ -783,10 +778,12 @@
return;
}
- final File profileFile = getPrimaryProfileFile(mPackageName);
-
- VMRuntime.registerAppInfo(profileFile.getPath(),
- codePaths.toArray(new String[codePaths.size()]));
+ for (int i = codePaths.size() - 1; i >= 0; i--) {
+ String splitName = i == 0 ? null : mApplicationInfo.splitNames[i - 1];
+ String profileFile = ArtManager.getCurrentProfilePath(
+ mPackageName, UserHandle.myUserId(), splitName);
+ VMRuntime.registerAppInfo(profileFile, new String[] {codePaths.get(i)});
+ }
// Register the app data directory with the reporter. It will
// help deciding whether or not a dex file is the primary apk or a
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d6fddfc..0b5b363 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -200,6 +200,16 @@
*/
private static final int MAX_REPLY_HISTORY = 5;
+
+ /**
+ * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
+ * we're adding the draft as a String extra to the {@link #contentIntent} using this key.
+ *
+ * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually
+ * sends messages.</p>
+ */
+ public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
+
/**
* A timestamp related to this notification, in milliseconds since the epoch.
*
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
new file mode 100644
index 0000000..c525c89c
--- /dev/null
+++ b/core/java/android/app/StatsManager.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2017 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.app;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.IBinder;
+import android.os.IStatsManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+/**
+ * API for statsd clients to send configurations and retrieve data.
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsManager extends android.util.StatsManager { // TODO: Remove the extends.
+ IStatsManager mService;
+ private static final String TAG = "StatsManager";
+
+ /** Long extra of uid that added the relevant stats config. */
+ public static final String EXTRA_STATS_CONFIG_UID =
+ "android.app.extra.STATS_CONFIG_UID";
+ /** Long extra of the relevant stats config's configKey. */
+ public static final String EXTRA_STATS_CONFIG_KEY =
+ "android.app.extra.STATS_CONFIG_KEY";
+ /** Long extra of the relevant statsd_config.proto's Subscription.id. */
+ public static final String EXTRA_STATS_SUBSCRIPTION_ID =
+ "android.app.extra.STATS_SUBSCRIPTION_ID";
+ /** Long extra of the relevant statsd_config.proto's Subscription.rule_id. */
+ public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID =
+ "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
+ /**
+ * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value
+ * information.
+ */
+ public static final String EXTRA_STATS_DIMENSIONS_VALUE =
+ "android.app.extra.STATS_DIMENSIONS_VALUE";
+
+ /**
+ * Broadcast Action: Statsd has started.
+ * Configurations and PendingIntents can now be sent to it.
+ */
+ public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
+
+ /**
+ * Constructor for StatsManagerClient.
+ *
+ * @hide
+ */
+ public StatsManager() {
+ }
+
+ /**
+ * Clients can send a configuration and simultaneously registers the name of a broadcast
+ * receiver that listens for when it should request data.
+ *
+ * @param configKey An arbitrary integer that allows clients to track the configuration.
+ * @param config Wire-encoded StatsDConfig proto that specifies metrics (and all
+ * dependencies eg, conditions and matchers).
+ * @param pkg The package name to receive the broadcast.
+ * @param cls The name of the class that receives the broadcast.
+ * @return true if successful
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.d(TAG, "Failed to find statsd when adding configuration");
+ return false;
+ }
+ return service.addConfiguration(configKey, config, pkg, cls);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connect to statsd when adding configuration");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Remove a configuration from logging.
+ *
+ * @param configKey Configuration key to remove.
+ * @return true if successful
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean removeConfiguration(long configKey) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.d(TAG, "Failed to find statsd when removing configuration");
+ return false;
+ }
+ return service.removeConfiguration(configKey);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connect to statsd when removing configuration");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Set the PendingIntent to be used when broadcasting subscriber information to the given
+ * subscriberId within the given config.
+ *
+ * <p>
+ * Suppose that the calling uid has added a config with key configKey, and that in this config
+ * it is specified that when a particular anomaly is detected, a broadcast should be sent to
+ * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
+ * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
+ * when the anomaly is detected.
+ *
+ * <p>
+ * When statsd sends the broadcast, the PendingIntent will used to send an intent with
+ * information of
+ * {@link #EXTRA_STATS_CONFIG_UID},
+ * {@link #EXTRA_STATS_CONFIG_KEY},
+ * {@link #EXTRA_STATS_SUBSCRIPTION_ID},
+ * {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, and
+ * {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
+ *
+ * <p>
+ * This function can only be called by the owner (uid) of the config. It must be called each
+ * time statsd starts. The config must have been added first (via addConfiguration()).
+ *
+ * @param configKey The integer naming the config to which this subscriber is attached.
+ * @param subscriberId ID of the subscriber, as used in the config.
+ * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
+ * associated with the given subscriberId. May be null, in which case
+ * it undoes any previous setting of this subscriberId.
+ * @return true if successful
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public boolean setBroadcastSubscriber(long configKey,
+ long subscriberId,
+ PendingIntent pendingIntent) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.w(TAG, "Failed to find statsd when adding broadcast subscriber");
+ return false;
+ }
+ if (pendingIntent != null) {
+ // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
+ IBinder intentSender = pendingIntent.getTarget().asBinder();
+ return service.setBroadcastSubscriber(configKey, subscriberId, intentSender);
+ } else {
+ return service.unsetBroadcastSubscriber(configKey, subscriberId);
+ }
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Clients can request data with a binder call. This getter is destructive and also clears
+ * the retrieved metrics from statsd memory.
+ *
+ * @param configKey Configuration key to retrieve data from.
+ * @return Serialized ConfigMetricsReportList proto. Returns null on failure.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public byte[] getData(long configKey) {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.d(TAG, "Failed to find statsd when getting data");
+ return null;
+ }
+ return service.getData(configKey);
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connecto statsd when getting data");
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Clients can request metadata for statsd. Will contain stats across all configurations but not
+ * the actual metrics themselves (metrics must be collected via {@link #getData(String)}.
+ * This getter is not destructive and will not reset any metrics/counters.
+ *
+ * @return Serialized StatsdStatsReport proto. Returns null on failure.
+ */
+ @RequiresPermission(Manifest.permission.DUMP)
+ public byte[] getMetadata() {
+ synchronized (this) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ Slog.d(TAG, "Failed to find statsd when getting metadata");
+ return null;
+ }
+ return service.getMetadata();
+ } catch (RemoteException e) {
+ Slog.d(TAG, "Failed to connecto statsd when getting metadata");
+ return null;
+ }
+ }
+ }
+
+ private class StatsdDeathRecipient implements IBinder.DeathRecipient {
+ @Override
+ public void binderDied() {
+ synchronized (this) {
+ mService = null;
+ }
+ }
+ }
+
+ private IStatsManager getIStatsManagerLocked() throws RemoteException {
+ if (mService != null) {
+ return mService;
+ }
+ mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+ if (mService != null) {
+ mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
+ }
+ return mService;
+ }
+}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index fb8d101..4310434 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -141,7 +141,6 @@
import android.telephony.euicc.EuiccCardManager;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
-import android.util.StatsManager;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.WindowManager;
diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java
index bab993f..ab59747 100644
--- a/core/java/android/app/TaskStackBuilder.java
+++ b/core/java/android/app/TaskStackBuilder.java
@@ -213,13 +213,13 @@
* Start the task stack constructed by this builder.
* @hide
*/
- public void startActivities(Bundle options, UserHandle userHandle) {
+ public int startActivities(Bundle options, UserHandle userHandle) {
if (mIntents.isEmpty()) {
throw new IllegalStateException(
"No intents added to TaskStackBuilder; cannot startActivities");
}
- mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle);
+ return mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle);
}
/**
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 861cb9a..72eb494 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -143,6 +143,26 @@
/** @hide */
public static final int TYPE_SYMLINK = 3;
+ /**
+ * Flag for {@link BackupDataOutput#getTransportFlags()} and
+ * {@link FullBackupDataOutput#getTransportFlags()} only.
+ *
+ * <p>The transport has client-side encryption enabled. i.e., the user's backup has been
+ * encrypted with a key known only to the device, and not to the remote storage solution. Even
+ * if an attacker had root access to the remote storage provider they should not be able to
+ * decrypt the user's backup data.
+ */
+ public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1;
+
+ /**
+ * Flag for {@link BackupDataOutput#getTransportFlags()} and
+ * {@link FullBackupDataOutput#getTransportFlags()} only.
+ *
+ * <p>The transport is for a device-to-device transfer. There is no third party or intermediate
+ * storage. The user's backup data is sent directly to another device over e.g., USB or WiFi.
+ */
+ public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2;
+
Handler mHandler = null;
Handler getHandler() {
@@ -920,12 +940,14 @@
public void doBackup(ParcelFileDescriptor oldState,
ParcelFileDescriptor data,
ParcelFileDescriptor newState,
- long quotaBytes, int token, IBackupManager callbackBinder) throws RemoteException {
+ long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags)
+ throws RemoteException {
// Ensure that we're running with the app's normal permission level
long ident = Binder.clearCallingIdentity();
if (DEBUG) Log.v(TAG, "doBackup() invoked");
- BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor(), quotaBytes);
+ BackupDataOutput output = new BackupDataOutput(
+ data.getFileDescriptor(), quotaBytes, transportFlags);
try {
BackupAgent.this.onBackup(oldState, output, newState);
@@ -999,7 +1021,7 @@
@Override
public void doFullBackup(ParcelFileDescriptor data,
- long quotaBytes, int token, IBackupManager callbackBinder) {
+ long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) {
// Ensure that we're running with the app's normal permission level
long ident = Binder.clearCallingIdentity();
@@ -1010,7 +1032,8 @@
waitForSharedPrefs();
try {
- BackupAgent.this.onFullBackup(new FullBackupDataOutput(data, quotaBytes));
+ BackupAgent.this.onFullBackup(new FullBackupDataOutput(
+ data, quotaBytes, transportFlags));
} catch (IOException ex) {
Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw new RuntimeException(ex);
@@ -1044,10 +1067,12 @@
}
}
- public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder) {
+ public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder,
+ int transportFlags) {
// Ensure that we're running with the app's normal permission level
final long ident = Binder.clearCallingIdentity();
- FullBackupDataOutput measureOutput = new FullBackupDataOutput(quotaBytes);
+ FullBackupDataOutput measureOutput =
+ new FullBackupDataOutput(quotaBytes, transportFlags);
waitForSharedPrefs();
try {
diff --git a/core/java/android/app/backup/BackupDataOutput.java b/core/java/android/app/backup/BackupDataOutput.java
index c7586a2..5a66f34 100644
--- a/core/java/android/app/backup/BackupDataOutput.java
+++ b/core/java/android/app/backup/BackupDataOutput.java
@@ -18,6 +18,7 @@
import android.annotation.SystemApi;
import android.os.ParcelFileDescriptor;
+
import java.io.FileDescriptor;
import java.io.IOException;
@@ -62,7 +63,10 @@
* @see BackupAgent
*/
public class BackupDataOutput {
- final long mQuota;
+
+ private final long mQuota;
+ private final int mTransportFlags;
+
long mBackupWriter;
/**
@@ -71,14 +75,20 @@
* @hide */
@SystemApi
public BackupDataOutput(FileDescriptor fd) {
- this(fd, -1);
+ this(fd, /*quota=*/ -1, /*transportFlags=*/ 0);
}
/** @hide */
@SystemApi
public BackupDataOutput(FileDescriptor fd, long quota) {
+ this(fd, quota, /*transportFlags=*/ 0);
+ }
+
+ /** @hide */
+ public BackupDataOutput(FileDescriptor fd, long quota, int transportFlags) {
if (fd == null) throw new NullPointerException();
mQuota = quota;
+ mTransportFlags = transportFlags;
mBackupWriter = ctor(fd);
if (mBackupWriter == 0) {
throw new RuntimeException("Native initialization failed with fd=" + fd);
@@ -96,6 +106,16 @@
}
/**
+ * Returns flags with additional information about the backup transport. For supported flags see
+ * {@link android.app.backup.BackupAgent}
+ *
+ * @see FullBackupDataOutput#getTransportFlags()
+ */
+ public int getTransportFlags() {
+ return mTransportFlags;
+ }
+
+ /**
* Mark the beginning of one record in the backup data stream. This must be called before
* {@link #writeEntityData}.
* @param key A string key that uniquely identifies the data record within the application.
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 12f4483..6ec0969 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -252,6 +252,8 @@
}
/**
+ * @deprecated Since Android P app can no longer request restoring of its backup.
+ *
* Restore the calling application from backup. The data will be restored from the
* current backup dataset if the application has stored data there, or from
* the dataset used during the last full device setup operation if the current
@@ -269,6 +271,7 @@
*
* @return Zero on success; nonzero on error.
*/
+ @Deprecated
public int requestRestore(RestoreObserver observer) {
return requestRestore(observer, null);
}
@@ -276,6 +279,8 @@
// system APIs start here
/**
+ * @deprecated Since Android P app can no longer request restoring of its backup.
+ *
* Restore the calling application from backup. The data will be restored from the
* current backup dataset if the application has stored data there, or from
* the dataset used during the last full device setup operation if the current
@@ -298,28 +303,12 @@
*
* @hide
*/
+ @Deprecated
@SystemApi
public int requestRestore(RestoreObserver observer, BackupManagerMonitor monitor) {
- int result = -1;
- checkServiceBinder();
- if (sService != null) {
- RestoreSession session = null;
- try {
- IRestoreSession binder = sService.beginRestoreSession(mContext.getPackageName(),
- null);
- if (binder != null) {
- session = new RestoreSession(mContext, binder);
- result = session.restorePackage(mContext.getPackageName(), observer, monitor);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "restoreSelf() unable to contact service");
- } finally {
- if (session != null) {
- session.endRestoreSession();
- }
- }
- }
- return result;
+ Log.w(TAG, "requestRestore(): Since Android P app can no longer request restoring"
+ + " of its backup.");
+ return -1;
}
/**
diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index a91aded..07e7688a 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -174,7 +174,6 @@
/**
* The transport returned {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED}.
- * @hide
*/
public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51;
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 266f58d..f456395 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -60,8 +60,6 @@
*
* <p>This is only valid when backup manager called {@link
* #performBackup(PackageInfo, ParcelFileDescriptor, int)} with {@link #FLAG_INCREMENTAL}.
- *
- * @hide
*/
public static final int TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = -1006;
@@ -73,7 +71,7 @@
* For key value backup, indicates that the backup data is a diff from a previous backup. The
* transport must apply this diff to an existing backup to build the new backup set.
*
- * @hide
+ * @see #performBackup(PackageInfo, ParcelFileDescriptor, int)
*/
public static final int FLAG_INCREMENTAL = 1 << 1;
@@ -81,7 +79,7 @@
* For key value backup, indicates that the backup data is a complete set, not a diff from a
* previous backup. The transport should clear any previous backup when storing this backup.
*
- * @hide
+ * @see #performBackup(PackageInfo, ParcelFileDescriptor, int)
*/
public static final int FLAG_NON_INCREMENTAL = 1 << 2;
@@ -609,6 +607,15 @@
}
/**
+ * Returns flags with additional information about the transport, which is accessible to the
+ * {@link android.app.backup.BackupAgent}. This allows the agent to decide what to do based on
+ * properties of the transport.
+ */
+ public int getTransportFlags() {
+ return 0;
+ }
+
+ /**
* Bridge between the actual IBackupTransport implementation and the stable API. If the
* binder interface needs to change, we use this layer to translate so that we can
* (if appropriate) decouple those framework-side changes from the BackupTransport
@@ -740,6 +747,11 @@
}
@Override
+ public int getTransportFlags() {
+ return BackupTransport.this.getTransportFlags();
+ }
+
+ @Override
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
return BackupTransport.this.getNextFullRestoreDataChunk(socket);
}
diff --git a/core/java/android/app/backup/FullBackupDataOutput.java b/core/java/android/app/backup/FullBackupDataOutput.java
index 5deedd0..18f4283 100644
--- a/core/java/android/app/backup/FullBackupDataOutput.java
+++ b/core/java/android/app/backup/FullBackupDataOutput.java
@@ -11,6 +11,7 @@
// Currently a name-scoping shim around BackupDataOutput
private final BackupDataOutput mData;
private final long mQuota;
+ private final int mTransportFlags;
private long mSize;
/**
@@ -23,22 +24,49 @@
return mQuota;
}
+ /**
+ * Returns flags with additional information about the backup transport. For supported flags see
+ * {@link android.app.backup.BackupAgent}
+ *
+ * @see BackupDataOutput#getTransportFlags()
+ */
+ public int getTransportFlags() {
+ return mTransportFlags;
+ }
+
/** @hide - used only in measure operation */
public FullBackupDataOutput(long quota) {
mData = null;
mQuota = quota;
mSize = 0;
+ mTransportFlags = 0;
+ }
+
+ /** @hide - used only in measure operation */
+ public FullBackupDataOutput(long quota, int transportFlags) {
+ mData = null;
+ mQuota = quota;
+ mSize = 0;
+ mTransportFlags = transportFlags;
}
/** @hide */
public FullBackupDataOutput(ParcelFileDescriptor fd, long quota) {
- mData = new BackupDataOutput(fd.getFileDescriptor(), quota);
+ mData = new BackupDataOutput(fd.getFileDescriptor(), quota, 0);
mQuota = quota;
+ mTransportFlags = 0;
+ }
+
+ /** @hide */
+ public FullBackupDataOutput(ParcelFileDescriptor fd, long quota, int transportFlags) {
+ mData = new BackupDataOutput(fd.getFileDescriptor(), quota, transportFlags);
+ mQuota = quota;
+ mTransportFlags = transportFlags;
}
/** @hide - used only internally to the backup manager service's stream construction */
public FullBackupDataOutput(ParcelFileDescriptor fd) {
- this(fd, -1);
+ this(fd, /*quota=*/ -1, /*transportFlags=*/ 0);
}
/** @hide */
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 35a21a4..b255a43 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -300,11 +300,7 @@
}
/**
- * Initiate connection to a profile of the remote bluetooth device.
- *
- * <p> Currently, the system supports only 1 connection to the
- * A2DP profile. The API will automatically disconnect connected
- * devices before connecting.
+ * Initiate connection to a profile of the remote Bluetooth device.
*
* <p> This API returns false in scenarios like the profile on the
* device is already connected or Bluetooth is not turned on.
@@ -699,15 +695,17 @@
/**
* Gets the current codec status (configuration and capability).
*
+ * @param device the remote Bluetooth device. If null, use the current
+ * active A2DP Bluetooth device.
* @return the current codec status
* @hide
*/
- public BluetoothCodecStatus getCodecStatus() {
- if (DBG) Log.d(TAG, "getCodecStatus");
+ public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
try {
mServiceLock.readLock().lock();
if (mService != null && isEnabled()) {
- return mService.getCodecStatus();
+ return mService.getCodecStatus(device);
}
if (mService == null) {
Log.w(TAG, "Proxy not attached to service");
@@ -724,15 +722,18 @@
/**
* Sets the codec configuration preference.
*
+ * @param device the remote Bluetooth device. If null, use the current
+ * active A2DP Bluetooth device.
* @param codecConfig the codec configuration preference
* @hide
*/
- public void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
- if (DBG) Log.d(TAG, "setCodecConfigPreference");
+ public void setCodecConfigPreference(BluetoothDevice device,
+ BluetoothCodecConfig codecConfig) {
+ if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")");
try {
mServiceLock.readLock().lock();
if (mService != null && isEnabled()) {
- mService.setCodecConfigPreference(codecConfig);
+ mService.setCodecConfigPreference(device, codecConfig);
}
if (mService == null) Log.w(TAG, "Proxy not attached to service");
return;
@@ -747,36 +748,42 @@
/**
* Enables the optional codecs.
*
+ * @param device the remote Bluetooth device. If null, use the currect
+ * active A2DP Bluetooth device.
* @hide
*/
- public void enableOptionalCodecs() {
- if (DBG) Log.d(TAG, "enableOptionalCodecs");
- enableDisableOptionalCodecs(true);
+ public void enableOptionalCodecs(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")");
+ enableDisableOptionalCodecs(device, true);
}
/**
* Disables the optional codecs.
*
+ * @param device the remote Bluetooth device. If null, use the currect
+ * active A2DP Bluetooth device.
* @hide
*/
- public void disableOptionalCodecs() {
- if (DBG) Log.d(TAG, "disableOptionalCodecs");
- enableDisableOptionalCodecs(false);
+ public void disableOptionalCodecs(BluetoothDevice device) {
+ if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")");
+ enableDisableOptionalCodecs(device, false);
}
/**
* Enables or disables the optional codecs.
*
+ * @param device the remote Bluetooth device. If null, use the currect
+ * active A2DP Bluetooth device.
* @param enable if true, enable the optional codecs, other disable them
*/
- private void enableDisableOptionalCodecs(boolean enable) {
+ private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
try {
mServiceLock.readLock().lock();
if (mService != null && isEnabled()) {
if (enable) {
- mService.enableOptionalCodecs();
+ mService.enableOptionalCodecs(device);
} else {
- mService.disableOptionalCodecs();
+ mService.disableOptionalCodecs(device);
}
}
if (mService == null) Log.w(TAG, "Proxy not attached to service");
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f69aab01..5177e10 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -32,6 +32,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.IServiceConnection;
import android.app.VrManager;
@@ -1834,13 +1835,17 @@
* See {@link android.content.Context#startActivity(Intent, Bundle)}
* Context.startActivity(Intent, Bundle)} for more details.
*
+ * @return The corresponding flag {@link ActivityManager#START_CANCELED},
+ * {@link ActivityManager#START_SUCCESS} etc. indicating whether the launch was
+ * successful.
+ *
* @throws ActivityNotFoundException
*
* @see #startActivities(Intent[])
* @see PackageManager#resolveActivity
*/
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
- public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+ public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
@@ -4121,7 +4126,7 @@
public static final String STATS_COMPANION_SERVICE = "statscompanion";
/**
- * Use with {@link #getSystemService(String)} to retrieve an {@link android.stats.StatsManager}.
+ * Use with {@link #getSystemService(String)} to retrieve an {@link android.app.StatsManager}.
* @hide
*/
@SystemApi
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 67de4fe..8c1293e 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -418,8 +418,8 @@
/** @hide */
@Override
- public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
- mBase.startActivitiesAsUser(intents, options, userHandle);
+ public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+ return mBase.startActivitiesAsUser(intents, options, userHandle);
}
@Override
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 13ec4fd..09a46b8 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -16,14 +16,10 @@
package android.content.pm;
-import android.annotation.IntDef;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
/**
* Overall information about the contents of a package. This corresponds
* to all of the information collected from AndroidManifest.xml.
@@ -370,28 +366,9 @@
public int overlayPriority;
/**
- * Flag for use with {@link #mOverlayFlags}. Marks the overlay as static, meaning it cannot
- * be enabled/disabled at runtime.
+ * Whether the overlay is static, meaning it cannot be enabled/disabled at runtime.
*/
- static final int FLAG_OVERLAY_STATIC = 1 << 1;
-
- /**
- * Flag for use with {@link #mOverlayFlags}. Marks the overlay as trusted (not 3rd party).
- */
- static final int FLAG_OVERLAY_TRUSTED = 1 << 2;
-
- @IntDef(flag = true, prefix = "FLAG_OVERLAY_", value = {
- FLAG_OVERLAY_STATIC,
- FLAG_OVERLAY_TRUSTED
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface OverlayFlags {}
-
- /**
- * Modifiers that affect the state of this overlay. See {@link #FLAG_OVERLAY_STATIC},
- * {@link #FLAG_OVERLAY_TRUSTED}.
- */
- @OverlayFlags int mOverlayFlags;
+ boolean mOverlayIsStatic;
/**
* The user-visible SDK version (ex. 26) of the framework against which the application claims
@@ -424,7 +401,7 @@
* @hide
*/
public boolean isOverlayPackage() {
- return overlayTarget != null && (mOverlayFlags & FLAG_OVERLAY_TRUSTED) != 0;
+ return overlayTarget != null;
}
/**
@@ -433,7 +410,7 @@
* @hide
*/
public boolean isStaticOverlayPackage() {
- return overlayTarget != null && (mOverlayFlags & FLAG_OVERLAY_STATIC) != 0;
+ return overlayTarget != null && mOverlayIsStatic;
}
@Override
@@ -488,7 +465,7 @@
dest.writeString(requiredAccountType);
dest.writeString(overlayTarget);
dest.writeInt(overlayPriority);
- dest.writeInt(mOverlayFlags);
+ dest.writeBoolean(mOverlayIsStatic);
dest.writeInt(compileSdkVersion);
dest.writeString(compileSdkVersionCodename);
}
@@ -543,7 +520,7 @@
requiredAccountType = source.readString();
overlayTarget = source.readString();
overlayPriority = source.readInt();
- mOverlayFlags = source.readInt();
+ mOverlayIsStatic = source.readBoolean();
compileSdkVersion = source.readInt();
compileSdkVersionCodename = source.readString();
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 67c9584..5a894c7 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1766,6 +1766,16 @@
"android.hardware.camera.capability.raw";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one
+ * of the cameras on the device supports the
+ * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING
+ * MOTION_TRACKING} capability level.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CAMERA_AR =
+ "android.hardware.camera.ar";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device is capable of communicating with
* consumer IR devices.
@@ -2594,6 +2604,14 @@
public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device has a StrongBox hardware-backed Keystore.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_STRONGBOX_KEYSTORE =
+ "android.hardware.strongbox_keystore";
+
+ /**
* Action to external storage service to clean out removed apps.
* @hide
*/
@@ -4740,7 +4758,7 @@
PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
if ((flags & GET_SIGNATURES) != 0) {
- PackageParser.collectCertificates(pkg, 0);
+ PackageParser.collectCertificates(pkg, false /* skipVerify */);
}
PackageUserState state = new PackageUserState();
return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5b5ccf5..3bb812b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -54,6 +54,7 @@
import android.content.pm.split.DefaultSplitAssetLoader;
import android.content.pm.split.SplitAssetDependencyLoader;
import android.content.pm.split.SplitAssetLoader;
+import android.content.res.ApkAssets;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -679,15 +680,7 @@
pi.requiredAccountType = p.mRequiredAccountType;
pi.overlayTarget = p.mOverlayTarget;
pi.overlayPriority = p.mOverlayPriority;
-
- if (p.mIsStaticOverlay) {
- pi.mOverlayFlags |= PackageInfo.FLAG_OVERLAY_STATIC;
- }
-
- if (p.mTrustedOverlay) {
- pi.mOverlayFlags |= PackageInfo.FLAG_OVERLAY_TRUSTED;
- }
-
+ pi.mOverlayIsStatic = p.mOverlayIsStatic;
pi.compileSdkVersion = p.mCompileSdkVersion;
pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename;
pi.firstInstallTime = firstInstallTime;
@@ -1504,9 +1497,9 @@
* populating {@link Package#mSigningDetails}. Also asserts that all APK
* contents are signed correctly and consistently.
*/
- public static void collectCertificates(Package pkg, @ParseFlags int parseFlags)
+ public static void collectCertificates(Package pkg, boolean skipVerify)
throws PackageParserException {
- collectCertificatesInternal(pkg, parseFlags);
+ collectCertificatesInternal(pkg, skipVerify);
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
Package childPkg = pkg.childPackages.get(i);
@@ -1514,17 +1507,17 @@
}
}
- private static void collectCertificatesInternal(Package pkg, @ParseFlags int parseFlags)
+ private static void collectCertificatesInternal(Package pkg, boolean skipVerify)
throws PackageParserException {
pkg.mSigningDetails = SigningDetails.UNKNOWN;
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
try {
- collectCertificates(pkg, new File(pkg.baseCodePath), parseFlags);
+ collectCertificates(pkg, new File(pkg.baseCodePath), skipVerify);
if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
for (int i = 0; i < pkg.splitCodePaths.length; i++) {
- collectCertificates(pkg, new File(pkg.splitCodePaths[i]), parseFlags);
+ collectCertificates(pkg, new File(pkg.splitCodePaths[i]), skipVerify);
}
}
} finally {
@@ -1532,7 +1525,7 @@
}
}
- private static void collectCertificates(Package pkg, File apkFile, @ParseFlags int parseFlags)
+ private static void collectCertificates(Package pkg, File apkFile, boolean skipVerify)
throws PackageParserException {
final String apkPath = apkFile.getAbsolutePath();
@@ -1542,7 +1535,7 @@
minSignatureScheme = SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2;
}
SigningDetails verified;
- if ((parseFlags & PARSE_IS_SYSTEM_DIR) != 0) {
+ if (skipVerify) {
// systemDir APKs are already trusted, save time by not verifying
verified = ApkSignatureVerifier.plsCertsNoVerifyOnlyCerts(
apkPath, minSignatureScheme);
@@ -1602,29 +1595,28 @@
int flags) throws PackageParserException {
final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
- AssetManager assets = null;
+ ApkAssets apkAssets = null;
XmlResourceParser parser = null;
try {
- assets = newConfiguredAssetManager();
- int cookie = fd != null
- ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath);
- if (cookie == 0) {
+ try {
+ apkAssets = fd != null
+ ? ApkAssets.loadFromFd(fd, debugPathName, false, false)
+ : ApkAssets.loadFromPath(apkPath);
+ } catch (IOException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"Failed to parse " + apkPath);
}
- final DisplayMetrics metrics = new DisplayMetrics();
- metrics.setToDefaults();
-
- parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+ parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME);
final SigningDetails signingDetails;
if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
// TODO: factor signature related items out of Package object
final Package tempPkg = new Package((String) null);
+ final boolean skipVerify = (flags & PARSE_IS_SYSTEM_DIR) != 0;
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
try {
- collectCertificates(tempPkg, apkFile, flags);
+ collectCertificates(tempPkg, apkFile, skipVerify);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
@@ -1642,7 +1634,7 @@
"Failed to parse " + apkPath, e);
} finally {
IoUtils.closeQuietly(parser);
- IoUtils.closeQuietly(assets);
+ IoUtils.closeQuietly(apkAssets);
}
}
@@ -2085,7 +2077,7 @@
pkg.mOverlayPriority = sa.getInt(
com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority,
0);
- pkg.mIsStaticOverlay = sa.getBoolean(
+ pkg.mOverlayIsStatic = sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic,
false);
final String propName = sa.getString(
@@ -6094,8 +6086,7 @@
public String mOverlayTarget;
public int mOverlayPriority;
- public boolean mIsStaticOverlay;
- public boolean mTrustedOverlay;
+ public boolean mOverlayIsStatic;
public int mCompileSdkVersion;
public String mCompileSdkVersionCodename;
@@ -6625,8 +6616,7 @@
mRequiredAccountType = dest.readString();
mOverlayTarget = dest.readString();
mOverlayPriority = dest.readInt();
- mIsStaticOverlay = (dest.readInt() == 1);
- mTrustedOverlay = (dest.readInt() == 1);
+ mOverlayIsStatic = (dest.readInt() == 1);
mCompileSdkVersion = dest.readInt();
mCompileSdkVersionCodename = dest.readString();
mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
@@ -6749,8 +6739,7 @@
dest.writeString(mRequiredAccountType);
dest.writeString(mOverlayTarget);
dest.writeInt(mOverlayPriority);
- dest.writeInt(mIsStaticOverlay ? 1 : 0);
- dest.writeInt(mTrustedOverlay ? 1 : 0);
+ dest.writeInt(mOverlayIsStatic ? 1 : 0);
dest.writeInt(mCompileSdkVersion);
dest.writeString(mCompileSdkVersionCodename);
dest.writeArraySet(mUpgradeKeySets);
diff --git a/core/java/android/content/pm/dex/ArtManager.java b/core/java/android/content/pm/dex/ArtManager.java
index aa9c46e6..4129398 100644
--- a/core/java/android/content/pm/dex/ArtManager.java
+++ b/core/java/android/content/pm/dex/ArtManager.java
@@ -16,16 +16,22 @@
package android.content.pm.dex;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
+import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
/**
* Class for retrieving various kinds of information related to the runtime artifacts of
* packages that are currently installed on the device.
@@ -43,6 +49,20 @@
/** The snapshot failed because of an internal error (e.g. error during opening profiles). */
public static final int SNAPSHOT_FAILED_INTERNAL_ERROR = 2;
+ /** Constant used for applications profiles. */
+ public static final int PROFILE_APPS = 0;
+ /** Constant used for the boot image profile. */
+ public static final int PROFILE_BOOT_IMAGE = 1;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "PROFILE_" }, value = {
+ PROFILE_APPS,
+ PROFILE_BOOT_IMAGE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProfileType {}
+
+
private IArtManager mArtManager;
/**
@@ -53,41 +73,59 @@
}
/**
- * Snapshots the runtime profile for an apk belonging to the package {@code packageName}.
- * The apk is identified by {@code codePath}. The calling process must have
- * {@code android.permission.READ_RUNTIME_PROFILE} permission.
+ * Snapshots a runtime profile according to the {@code profileType} parameter.
*
- * The result will be posted on {@code handler} using the given {@code callback}.
- * The profile being available as a read-only {@link android.os.ParcelFileDescriptor}.
+ * If {@code profileType} is {@link ArtManager#PROFILE_APPS} the method will snapshot
+ * the profile for for an apk belonging to the package {@code packageName}.
+ * The apk is identified by {@code codePath}.
*
- * @param packageName the target package name
- * @param codePath the code path for which the profile should be retrieved
+ * If {@code profileType} is {@code ArtManager.PROFILE_BOOT_IMAGE} the method will snapshot
+ * the profile for the boot image. In this case {@code codePath can be null}. The parameters
+ * {@code packageName} and {@code codePath} are ignored.
+ *u
+ * The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission.
+ *
+ * The result will be posted on the {@code executor} using the given {@code callback}.
+ * The profile will be available as a read-only {@link android.os.ParcelFileDescriptor}.
+ *
+ * This method will throw {@link IllegalStateException} if
+ * {@link ArtManager#isRuntimeProfilingEnabled(int)} does not return true for the given
+ * {@code profileType}.
+ *
+ * @param profileType the type of profile that should be snapshot (boot image or app)
+ * @param packageName the target package name or null if the target is the boot image
+ * @param codePath the code path for which the profile should be retrieved or null if
+ * the target is the boot image
* @param callback the callback which should be used for the result
- * @param handler the handler which should be used to post the result
+ * @param executor the executor which should be used to post the result
*/
@RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES)
- public void snapshotRuntimeProfile(@NonNull String packageName, @NonNull String codePath,
- @NonNull SnapshotRuntimeProfileCallback callback, @NonNull Handler handler) {
+ public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName,
+ @Nullable String codePath, @NonNull @CallbackExecutor Executor executor,
+ @NonNull SnapshotRuntimeProfileCallback callback) {
Slog.d(TAG, "Requesting profile snapshot for " + packageName + ":" + codePath);
SnapshotRuntimeProfileCallbackDelegate delegate =
- new SnapshotRuntimeProfileCallbackDelegate(callback, handler.getLooper());
+ new SnapshotRuntimeProfileCallbackDelegate(callback, executor);
try {
- mArtManager.snapshotRuntimeProfile(packageName, codePath, delegate);
+ mArtManager.snapshotRuntimeProfile(profileType, packageName, codePath, delegate);
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
}
/**
- * Returns true if runtime profiles are enabled, false otherwise.
+ * Returns true if runtime profiles are enabled for the given type, false otherwise.
*
* The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission.
+ *
+ * @param profileType can be either {@link ArtManager#PROFILE_APPS}
+ * or {@link ArtManager#PROFILE_BOOT_IMAGE}
*/
@RequiresPermission(android.Manifest.permission.READ_RUNTIME_PROFILES)
- public boolean isRuntimeProfilingEnabled() {
+ public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) {
try {
- return mArtManager.isRuntimeProfilingEnabled();
+ return mArtManager.isRuntimeProfilingEnabled(profileType);
} catch (RemoteException e) {
e.rethrowAsRuntimeException();
}
@@ -116,41 +154,24 @@
}
private static class SnapshotRuntimeProfileCallbackDelegate
- extends android.content.pm.dex.ISnapshotRuntimeProfileCallback.Stub
- implements Handler.Callback {
- private static final int MSG_SNAPSHOT_OK = 1;
- private static final int MSG_ERROR = 2;
+ extends android.content.pm.dex.ISnapshotRuntimeProfileCallback.Stub {
private final ArtManager.SnapshotRuntimeProfileCallback mCallback;
- private final Handler mHandler;
+ private final Executor mExecutor;
private SnapshotRuntimeProfileCallbackDelegate(
- ArtManager.SnapshotRuntimeProfileCallback callback, Looper looper) {
+ ArtManager.SnapshotRuntimeProfileCallback callback, Executor executor) {
mCallback = callback;
- mHandler = new Handler(looper, this);
+ mExecutor = executor;
}
@Override
- public void onSuccess(ParcelFileDescriptor profileReadFd) {
- mHandler.obtainMessage(MSG_SNAPSHOT_OK, profileReadFd).sendToTarget();
+ public void onSuccess(final ParcelFileDescriptor profileReadFd) {
+ mExecutor.execute(() -> mCallback.onSuccess(profileReadFd));
}
@Override
public void onError(int errCode) {
- mHandler.obtainMessage(MSG_ERROR, errCode, 0).sendToTarget();
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SNAPSHOT_OK:
- mCallback.onSuccess((ParcelFileDescriptor) msg.obj);
- break;
- case MSG_ERROR:
- mCallback.onError(msg.arg1);
- break;
- default: return false;
- }
- return true;
+ mExecutor.execute(() -> mCallback.onError(errCode));
}
}
@@ -163,4 +184,27 @@
public static String getProfileName(String splitName) {
return splitName == null ? "primary.prof" : splitName + ".split.prof";
}
+
+ /**
+ * Return the path to the current profile corresponding to given package and split.
+ *
+ * @hide
+ */
+ public static String getCurrentProfilePath(String packageName, int userId, String splitName) {
+ File profileDir = Environment.getDataProfilesDePackageDirectory(userId, packageName);
+ return new File(profileDir, getProfileName(splitName)).getAbsolutePath();
+ }
+
+ /**
+ * Return the snapshot profile file for the given package and profile name.
+ *
+ * KEEP in sync with installd dexopt.cpp.
+ * TODO(calin): inject the snapshot profile name from PM to avoid the dependency.
+ *
+ * @hide
+ */
+ public static File getProfileSnapshotFileForName(String packageName, String profileName) {
+ File profileDir = Environment.getDataRefProfilesDePackageDirectory(packageName);
+ return new File(profileDir, profileName + ".snapshot");
+ }
}
diff --git a/core/java/android/content/pm/dex/IArtManager.aidl b/core/java/android/content/pm/dex/IArtManager.aidl
index 8cbb452..6abfdba 100644
--- a/core/java/android/content/pm/dex/IArtManager.aidl
+++ b/core/java/android/content/pm/dex/IArtManager.aidl
@@ -25,20 +25,34 @@
*/
interface IArtManager {
/**
- * Snapshots the runtime profile for an apk belonging to the package {@param packageName}.
- * The apk is identified by {@param codePath}. The calling process must have
- * {@code android.permission.READ_RUNTIME_PROFILE} permission.
+ * Snapshots a runtime profile according to the {@code profileType} parameter.
*
- * The result will be posted on {@param callback} with the profile being available as a
- * read-only {@link android.os.ParcelFileDescriptor}.
- */
- oneway void snapshotRuntimeProfile(in String packageName,
- in String codePath, in ISnapshotRuntimeProfileCallback callback);
-
- /**
- * Returns true if runtime profiles are enabled, false otherwise.
+ * If {@code profileType} is {@link ArtManager#PROFILE_APPS} the method will snapshot
+ * the profile for for an apk belonging to the package {@code packageName}.
+ * The apk is identified by {@code codePath}.
+ *
+ * If {@code profileType} is {@code ArtManager.PROFILE_BOOT_IMAGE} the method will snapshot
+ * the profile for the boot image. In this case {@code codePath can be null}. The parameters
+ * {@code packageName} and {@code codePath} are ignored.
*
* The calling process must have {@code android.permission.READ_RUNTIME_PROFILE} permission.
+ *
+ * The result will be posted on the {@code executor} using the given {@code callback}.
+ * The profile will be available as a read-only {@link android.os.ParcelFileDescriptor}.
+ *
+ * This method will throw {@link IllegalStateException} if
+ * {@link ArtManager#isRuntimeProfilingEnabled(int)} does not return true for the given
+ * {@code profileType}.
*/
- boolean isRuntimeProfilingEnabled();
+ oneway void snapshotRuntimeProfile(int profileType, in String packageName,
+ in String codePath, in ISnapshotRuntimeProfileCallback callback);
+
+ /**
+ * Returns true if runtime profiles are enabled for the given type, false otherwise.
+ * The type can be can be either {@code ArtManager.PROFILE_APPS}
+ * or {@code ArtManager.PROFILE_BOOT_IMAGE}.
+ *
+ * @param profileType
+ */
+ boolean isRuntimeProfilingEnabled(int profileType);
}
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
new file mode 100644
index 0000000..b087c48
--- /dev/null
+++ b/core/java/android/content/res/ApkAssets.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2017 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.content.res;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * The loaded, immutable, in-memory representation of an APK.
+ *
+ * The main implementation is native C++ and there is very little API surface exposed here. The APK
+ * is mainly accessed via {@link AssetManager}.
+ *
+ * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers,
+ * making the creation of AssetManagers very cheap.
+ * @hide
+ */
+public final class ApkAssets implements AutoCloseable {
+ @GuardedBy("this") private long mNativePtr;
+ @GuardedBy("this") private StringBlock mStringBlock;
+
+ /**
+ * Creates a new ApkAssets instance from the given path on disk.
+ *
+ * @param path The path to an APK on disk.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
+ return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given path on disk.
+ *
+ * @param path The path to an APK on disk.
+ * @param system When true, the APK is loaded as a system APK (framework).
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system)
+ throws IOException {
+ return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given path on disk.
+ *
+ * @param path The path to an APK on disk.
+ * @param system When true, the APK is loaded as a system APK (framework).
+ * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are
+ * loaded as a shared library.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
+ boolean forceSharedLibrary) throws IOException {
+ return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor. Not for use by applications.
+ *
+ * Performs a dup of the underlying fd, so you must take care of still closing
+ * the FileDescriptor yourself (and can do that whenever you want).
+ *
+ * @param fd The FileDescriptor of an open, readable APK.
+ * @param friendlyName The friendly name used to identify this ApkAssets when logging.
+ * @param system When true, the APK is loaded as a system APK (framework).
+ * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are
+ * loaded as a shared library.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
+ @NonNull String friendlyName, boolean system, boolean forceSharedLibrary)
+ throws IOException {
+ return new ApkAssets(fd, friendlyName, system, forceSharedLibrary);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path
+ * is encoded within the IDMAP.
+ *
+ * @param idmapPath Path to the IDMAP of an overlay APK.
+ * @param system When true, the APK is loaded as a system APK (framework).
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
+ throws IOException {
+ return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/);
+ }
+
+ private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+ throws IOException {
+ Preconditions.checkNotNull(path, "path");
+ mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
+ mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ }
+
+ private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system,
+ boolean forceSharedLib) throws IOException {
+ Preconditions.checkNotNull(fd, "fd");
+ Preconditions.checkNotNull(friendlyName, "friendlyName");
+ mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib);
+ mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ }
+
+ @NonNull String getAssetPath() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetAssetPath(mNativePtr);
+ }
+ }
+
+ CharSequence getStringFromPool(int idx) {
+ synchronized (this) {
+ ensureValidLocked();
+ return mStringBlock.get(idx);
+ }
+ }
+
+ /**
+ * Retrieve a parser for a compiled XML file. This is associated with a single APK and
+ * <em>NOT</em> a full AssetManager. This means that shared-library references will not be
+ * dynamically assigned runtime package IDs.
+ *
+ * @param fileName The path to the file within the APK.
+ * @return An XmlResourceParser.
+ * @throws IOException if the file was not found or an error occurred retrieving it.
+ */
+ public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
+ synchronized (this) {
+ ensureValidLocked();
+ long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
+ try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
+ XmlResourceParser parser = block.newParser();
+ // If nativeOpenXml doesn't throw, it will always return a valid native pointer,
+ // which makes newParser always return non-null. But let's be paranoid.
+ if (parser == null) {
+ throw new AssertionError("block.newParser() returned a null parser");
+ }
+ return parser;
+ }
+ }
+ }
+
+ /**
+ * Returns false if the underlying APK was changed since this ApkAssets was loaded.
+ */
+ public boolean isUpToDate() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeIsUpToDate(mNativePtr);
+ }
+ }
+
+ /**
+ * Closes the ApkAssets and destroys the underlying native implementation. Further use of the
+ * ApkAssets object will cause exceptions to be thrown.
+ *
+ * Calling close on an already closed ApkAssets does nothing.
+ */
+ @Override
+ public void close() {
+ synchronized (this) {
+ if (mNativePtr == 0) {
+ return;
+ }
+
+ mStringBlock = null;
+ nativeDestroy(mNativePtr);
+ mNativePtr = 0;
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ if (mNativePtr != 0) {
+ nativeDestroy(mNativePtr);
+ }
+ }
+
+ private void ensureValidLocked() {
+ if (mNativePtr == 0) {
+ throw new RuntimeException("ApkAssets is closed");
+ }
+ }
+
+ private static native long nativeLoad(
+ @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+ throws IOException;
+ private static native long nativeLoadFromFd(@NonNull FileDescriptor fd,
+ @NonNull String friendlyName, boolean system, boolean forceSharedLib)
+ throws IOException;
+ private static native void nativeDestroy(long ptr);
+ private static native @NonNull String nativeGetAssetPath(long ptr);
+ private static native long nativeGetStringBlock(long ptr);
+ private static native boolean nativeIsUpToDate(long ptr);
+ private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
+}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 7866560..78370f4 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -18,9 +18,11 @@
import android.annotation.AnyRes;
import android.annotation.ArrayRes;
+import android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
+import android.annotation.StyleRes;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration.NativeConfig;
import android.os.ParcelFileDescriptor;
@@ -28,10 +30,18 @@
import android.util.SparseArray;
import android.util.TypedValue;
-import java.io.FileDescriptor;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.channels.FileLock;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
/**
@@ -42,7 +52,17 @@
* bytes.
*/
public final class AssetManager implements AutoCloseable {
- /* modes used when opening an asset */
+ private static final String TAG = "AssetManager";
+ private static final boolean DEBUG_REFS = false;
+
+ private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk";
+
+ private static final Object sSync = new Object();
+
+ // Not private for LayoutLib's BridgeAssetManager.
+ @GuardedBy("sSync") static AssetManager sSystem = null;
+
+ @GuardedBy("sSync") private static ApkAssets[] sSystemApkAssets = new ApkAssets[0];
/**
* Mode for {@link #open(String, int)}: no specific information about how
@@ -65,146 +85,246 @@
*/
public static final int ACCESS_BUFFER = 3;
- private static final String TAG = "AssetManager";
- private static final boolean localLOGV = false || false;
-
- private static final boolean DEBUG_REFS = false;
-
- private static final Object sSync = new Object();
- /*package*/ static AssetManager sSystem = null;
+ @GuardedBy("this") private final TypedValue mValue = new TypedValue();
+ @GuardedBy("this") private final long[] mOffsets = new long[2];
- private final TypedValue mValue = new TypedValue();
- private final long[] mOffsets = new long[2];
-
- // For communication with native code.
- private long mObject;
+ // Pointer to native implementation, stuffed inside a long.
+ @GuardedBy("this") private long mObject;
- private StringBlock mStringBlocks[] = null;
-
- private int mNumRefs = 1;
- private boolean mOpen = true;
- private HashMap<Long, RuntimeException> mRefStacks;
-
+ // The loaded asset paths.
+ @GuardedBy("this") private ApkAssets[] mApkAssets;
+
+ // Debug/reference counting implementation.
+ @GuardedBy("this") private boolean mOpen = true;
+ @GuardedBy("this") private int mNumRefs = 1;
+ @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;
+
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
* appropriate asset manager with {@link Resources#getAssets}. Not for
* use by applications.
- * {@hide}
+ * @hide
*/
public AssetManager() {
- synchronized (this) {
- if (DEBUG_REFS) {
- mNumRefs = 0;
- incRefsLocked(this.hashCode());
- }
- init(false);
- if (localLOGV) Log.v(TAG, "New asset manager: " + this);
- ensureSystemAssets();
+ final ApkAssets[] assets;
+ synchronized (sSync) {
+ createSystemAssetsInZygoteLocked();
+ assets = sSystemApkAssets;
+ }
+
+ mObject = nativeCreate();
+ if (DEBUG_REFS) {
+ mNumRefs = 0;
+ incRefsLocked(hashCode());
+ }
+
+ // Always set the framework resources.
+ setApkAssets(assets, false /*invalidateCaches*/);
+ }
+
+ /**
+ * Private constructor that doesn't call ensureSystemAssets.
+ * Used for the creation of system assets.
+ */
+ @SuppressWarnings("unused")
+ private AssetManager(boolean sentinel) {
+ mObject = nativeCreate();
+ if (DEBUG_REFS) {
+ mNumRefs = 0;
+ incRefsLocked(hashCode());
}
}
- private static void ensureSystemAssets() {
- synchronized (sSync) {
- if (sSystem == null) {
- AssetManager system = new AssetManager(true);
- system.makeStringBlocks(null);
- sSystem = system;
- }
+ /**
+ * This must be called from Zygote so that system assets are shared by all applications.
+ * @hide
+ */
+ private static void createSystemAssetsInZygoteLocked() {
+ if (sSystem != null) {
+ return;
}
- }
-
- private AssetManager(boolean isSystem) {
- if (DEBUG_REFS) {
- synchronized (this) {
- mNumRefs = 0;
- incRefsLocked(this.hashCode());
+
+ // Make sure that all IDMAPs are up to date.
+ nativeVerifySystemIdmaps();
+
+ try {
+ ArrayList<ApkAssets> apkAssets = new ArrayList<>();
+ apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/));
+
+ // Load all static RROs.
+ try (FileInputStream fis = new FileInputStream(
+ "/data/resource-cache/overlays.list");
+ BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
+ // Acquire a lock so that any idmap scanning doesn't impact the current set.
+ try (FileLock flock = fis.getChannel().lock(0, Long.MAX_VALUE,
+ true /*shared*/)) {
+ for (String line; (line = br.readLine()) != null; ) {
+ String idmapPath = line.split(" ")[1];
+ apkAssets.add(
+ ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
+ }
+ }
}
+
+ sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]);
+ sSystem = new AssetManager(true /*sentinel*/);
+ sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/);
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to create system AssetManager", e);
}
- init(true);
- if (localLOGV) Log.v(TAG, "New asset manager: " + this);
}
/**
* Return a global shared asset manager that provides access to only
* system assets (no application assets).
- * {@hide}
+ * @hide
*/
public static AssetManager getSystem() {
- ensureSystemAssets();
- return sSystem;
+ synchronized (sSync) {
+ createSystemAssetsInZygoteLocked();
+ return sSystem;
+ }
}
/**
* Close this asset manager.
*/
+ @Override
public void close() {
- synchronized(this) {
- //System.out.println("Release: num=" + mNumRefs
- // + ", released=" + mReleased);
- if (mOpen) {
- mOpen = false;
- decRefsLocked(this.hashCode());
- }
- }
- }
-
- /**
- * Retrieves the string value associated with a particular resource
- * identifier for the current configuration.
- *
- * @param resId the resource identifier to load
- * @return the string value, or {@code null}
- */
- @Nullable
- final CharSequence getResourceText(@StringRes int resId) {
synchronized (this) {
- final TypedValue outValue = mValue;
- if (getResourceValue(resId, 0, outValue, true)) {
- return outValue.coerceToString();
+ if (!mOpen) {
+ return;
}
- return null;
+
+ mOpen = false;
+ decRefsLocked(hashCode());
}
}
/**
- * Retrieves the string value associated with a particular resource
- * identifier for the current configuration.
+ * Changes the asset paths in this AssetManager. This replaces the {@link #addAssetPath(String)}
+ * family of methods.
*
- * @param resId the resource identifier to load
- * @param bagEntryId
- * @return the string value, or {@code null}
+ * @param apkAssets The new set of paths.
+ * @param invalidateCaches Whether to invalidate any caches. This should almost always be true.
+ * Set this to false if you are appending new resources
+ * (not new configurations).
+ * @hide
*/
- @Nullable
- final CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) {
+ public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) {
+ Preconditions.checkNotNull(apkAssets, "apkAssets");
synchronized (this) {
- final TypedValue outValue = mValue;
- final int block = loadResourceBagValue(resId, bagEntryId, outValue, true);
- if (block < 0) {
- return null;
+ ensureValidLocked();
+ mApkAssets = apkAssets;
+ nativeSetApkAssets(mObject, apkAssets, invalidateCaches);
+ if (invalidateCaches) {
+ // Invalidate all caches.
+ invalidateCachesLocked(-1);
}
-
- // Convert the changing configurations flags populated by native code.
- outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
- outValue.changingConfigurations);
-
- if (outValue.type == TypedValue.TYPE_STRING) {
- return mStringBlocks[block].get(outValue.data);
- }
- return outValue.coerceToString();
}
}
/**
- * Retrieves the string array associated with a particular resource
- * identifier for the current configuration.
+ * Invalidates the caches in this AssetManager according to the bitmask `diff`.
*
- * @param resId the resource identifier of the string array
- * @return the string array, or {@code null}
+ * @param diff The bitmask of changes generated by {@link Configuration#diff(Configuration)}.
+ * @see ActivityInfo.Config
*/
- @Nullable
- final String[] getResourceStringArray(@ArrayRes int resId) {
- return getArrayStringResource(resId);
+ private void invalidateCachesLocked(int diff) {
+ // TODO(adamlesinski): Currently there are no caches to invalidate in Java code.
+ }
+
+ /**
+ * @hide
+ */
+ public @NonNull ApkAssets[] getApkAssets() {
+ synchronized (this) {
+ ensureValidLocked();
+ return mApkAssets;
+ }
+ }
+
+ /**
+ * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
+ * @hide
+ */
+ @Deprecated
+ public int addAssetPath(String path) {
+ return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
+ }
+
+ /**
+ * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
+ * @hide
+ */
+ @Deprecated
+ public int addAssetPathAsSharedLibrary(String path) {
+ return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/);
+ }
+
+ /**
+ * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
+ * @hide
+ */
+ @Deprecated
+ public int addOverlayPath(String path) {
+ return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/);
+ }
+
+ private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
+ Preconditions.checkNotNull(path, "path");
+ synchronized (this) {
+ ensureOpenLocked();
+ final int count = mApkAssets.length;
+ for (int i = 0; i < count; i++) {
+ if (mApkAssets[i].getAssetPath().equals(path)) {
+ return i + 1;
+ }
+ }
+
+ final ApkAssets assets;
+ try {
+ if (overlay) {
+ // TODO(b/70343104): This hardcoded path will be removed once
+ // addAssetPathInternal is deleted.
+ final String idmapPath = "/data/resource-cache/"
+ + path.substring(1).replace('/', '@')
+ + "@idmap";
+ assets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/);
+ } else {
+ assets = ApkAssets.loadFromPath(path, false /*system*/, appAsLib);
+ }
+ } catch (IOException e) {
+ return 0;
+ }
+
+ final ApkAssets[] newApkAssets = Arrays.copyOf(mApkAssets, count + 1);
+ newApkAssets[count] = assets;
+ setApkAssets(newApkAssets, true);
+ return count + 1;
+ }
+ }
+
+ /**
+ * Ensures that the native implementation has not been destroyed.
+ * The AssetManager may have been closed, but references to it still exist
+ * and therefore the native implementation is not destroyed.
+ */
+ private void ensureValidLocked() {
+ if (mObject == 0) {
+ throw new RuntimeException("AssetManager has been destroyed");
+ }
+ }
+
+ /**
+ * Ensures that the AssetManager has not been explicitly closed. If this method passes,
+ * then this implies that ensureValidLocked() also passes.
+ */
+ private void ensureOpenLocked() {
+ if (!mOpen) {
+ throw new RuntimeException("AssetManager has been closed");
+ }
}
/**
@@ -219,11 +339,14 @@
* @return {@code true} if the data was loaded into {@code outValue},
* {@code false} otherwise
*/
- final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
+ boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
+ Preconditions.checkNotNull(outValue, "outValue");
synchronized (this) {
- final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
- if (block < 0) {
+ ensureValidLocked();
+ final int cookie = nativeGetResourceValue(
+ mObject, resId, (short) densityDpi, outValue, resolveRefs);
+ if (cookie <= 0) {
return false;
}
@@ -232,38 +355,156 @@
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
- outValue.string = mStringBlocks[block].get(outValue.data);
+ outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
}
return true;
}
}
/**
+ * Retrieves the string value associated with a particular resource
+ * identifier for the current configuration.
+ *
+ * @param resId the resource identifier to load
+ * @return the string value, or {@code null}
+ */
+ @Nullable CharSequence getResourceText(@StringRes int resId) {
+ synchronized (this) {
+ final TypedValue outValue = mValue;
+ if (getResourceValue(resId, 0, outValue, true)) {
+ return outValue.coerceToString();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Retrieves the string value associated with a particular resource
+ * identifier for the current configuration.
+ *
+ * @param resId the resource identifier to load
+ * @param bagEntryId the index into the bag to load
+ * @return the string value, or {@code null}
+ */
+ @Nullable CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) {
+ synchronized (this) {
+ ensureValidLocked();
+ final TypedValue outValue = mValue;
+ final int cookie = nativeGetResourceBagValue(mObject, resId, bagEntryId, outValue);
+ if (cookie <= 0) {
+ return null;
+ }
+
+ // Convert the changing configurations flags populated by native code.
+ outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+ outValue.changingConfigurations);
+
+ if (outValue.type == TypedValue.TYPE_STRING) {
+ return mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ }
+ return outValue.coerceToString();
+ }
+ }
+
+ int getResourceArraySize(@ArrayRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceArraySize(mObject, resId);
+ }
+ }
+
+ /**
+ * Populates `outData` with array elements of `resId`. `outData` is normally
+ * used with
+ * {@link TypedArray}.
+ *
+ * Each logical element in `outData` is {@link TypedArray#STYLE_NUM_ENTRIES}
+ * long,
+ * with the indices of the data representing the type, value, asset cookie,
+ * resource ID,
+ * configuration change mask, and density of the element.
+ *
+ * @param resId The resource ID of an array resource.
+ * @param outData The array to populate with data.
+ * @return The length of the array.
+ *
+ * @see TypedArray#STYLE_TYPE
+ * @see TypedArray#STYLE_DATA
+ * @see TypedArray#STYLE_ASSET_COOKIE
+ * @see TypedArray#STYLE_RESOURCE_ID
+ * @see TypedArray#STYLE_CHANGING_CONFIGURATIONS
+ * @see TypedArray#STYLE_DENSITY
+ */
+ int getResourceArray(@ArrayRes int resId, @NonNull int[] outData) {
+ Preconditions.checkNotNull(outData, "outData");
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceArray(mObject, resId, outData);
+ }
+ }
+
+ /**
+ * Retrieves the string array associated with a particular resource
+ * identifier for the current configuration.
+ *
+ * @param resId the resource identifier of the string array
+ * @return the string array, or {@code null}
+ */
+ @Nullable String[] getResourceStringArray(@ArrayRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceStringArray(mObject, resId);
+ }
+ }
+
+ /**
* Retrieve the text array associated with a particular resource
* identifier.
*
* @param resId the resource id of the string array
*/
- final @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
+ @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
synchronized (this) {
- final int[] rawInfoArray = getArrayStringInfo(resId);
+ ensureValidLocked();
+ final int[] rawInfoArray = nativeGetResourceStringArrayInfo(mObject, resId);
if (rawInfoArray == null) {
return null;
}
+
final int rawInfoArrayLen = rawInfoArray.length;
final int infoArrayLen = rawInfoArrayLen / 2;
- int block;
- int index;
final CharSequence[] retArray = new CharSequence[infoArrayLen];
for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) {
- block = rawInfoArray[i];
- index = rawInfoArray[i + 1];
- retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null;
+ int cookie = rawInfoArray[i];
+ int index = rawInfoArray[i + 1];
+ retArray[j] = (index >= 0 && cookie > 0)
+ ? mApkAssets[cookie - 1].getStringFromPool(index) : null;
}
return retArray;
}
}
+ @Nullable int[] getResourceIntArray(@ArrayRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceIntArray(mObject, resId);
+ }
+ }
+
+ /**
+ * Get the attributes for a style resource. These are the <item>
+ * elements in
+ * a <style> resource.
+ * @param resId The resource ID of the style
+ * @return An array of attribute IDs.
+ */
+ @AttrRes int[] getStyleAttributes(@StyleRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetStyleAttributes(mObject, resId);
+ }
+ }
+
/**
* Populates {@code outValue} with the data associated with a particular
* resource identifier for the current configuration. Resolves theme
@@ -277,73 +518,88 @@
* @return {@code true} if the data was loaded into {@code outValue},
* {@code false} otherwise
*/
- final boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue,
+ boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue,
boolean resolveRefs) {
- final int block = loadThemeAttributeValue(theme, resId, outValue, resolveRefs);
- if (block < 0) {
- return false;
- }
-
- // Convert the changing configurations flags populated by native code.
- outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
- outValue.changingConfigurations);
-
- if (outValue.type == TypedValue.TYPE_STRING) {
- final StringBlock[] blocks = ensureStringBlocks();
- outValue.string = blocks[block].get(outValue.data);
- }
- return true;
- }
-
- /**
- * Ensures the string blocks are loaded.
- *
- * @return the string blocks
- */
- @NonNull
- final StringBlock[] ensureStringBlocks() {
+ Preconditions.checkNotNull(outValue, "outValue");
synchronized (this) {
- if (mStringBlocks == null) {
- makeStringBlocks(sSystem.mStringBlocks);
+ ensureValidLocked();
+ final int cookie = nativeThemeGetAttributeValue(mObject, theme, resId, outValue,
+ resolveRefs);
+ if (cookie <= 0) {
+ return false;
}
- return mStringBlocks;
+
+ // Convert the changing configurations flags populated by native code.
+ outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+ outValue.changingConfigurations);
+
+ if (outValue.type == TypedValue.TYPE_STRING) {
+ outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ }
+ return true;
}
}
- /*package*/ final void makeStringBlocks(StringBlock[] seed) {
- final int seedNum = (seed != null) ? seed.length : 0;
- final int num = getStringBlockCount();
- mStringBlocks = new StringBlock[num];
- if (localLOGV) Log.v(TAG, "Making string blocks for " + this
- + ": " + num);
- for (int i=0; i<num; i++) {
- if (i < seedNum) {
- mStringBlocks[i] = seed[i];
- } else {
- mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
- }
- }
- }
-
- /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) {
+ void dumpTheme(long theme, int priority, String tag, String prefix) {
synchronized (this) {
- // Cookies map to string blocks starting at 1.
- return mStringBlocks[cookie - 1].get(id);
+ ensureValidLocked();
+ nativeThemeDump(mObject, theme, priority, tag, prefix);
}
}
+ @Nullable String getResourceName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceName(mObject, resId);
+ }
+ }
+
+ @Nullable String getResourcePackageName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourcePackageName(mObject, resId);
+ }
+ }
+
+ @Nullable String getResourceTypeName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceTypeName(mObject, resId);
+ }
+ }
+
+ @Nullable String getResourceEntryName(@AnyRes int resId) {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetResourceEntryName(mObject, resId);
+ }
+ }
+
+ @AnyRes int getResourceIdentifier(@NonNull String name, @Nullable String defType,
+ @Nullable String defPackage) {
+ synchronized (this) {
+ ensureValidLocked();
+ // name is checked in JNI.
+ return nativeGetResourceIdentifier(mObject, name, defType, defPackage);
+ }
+ }
+
+ CharSequence getPooledStringForCookie(int cookie, int id) {
+ // Cookies map to ApkAssets starting at 1.
+ return getApkAssets()[cookie - 1].getStringFromPool(id);
+ }
+
/**
* Open an asset using ACCESS_STREAMING mode. This provides access to
* files that have been bundled with an application as assets -- that is,
* files placed in to the "assets" directory.
*
- * @param fileName The name of the asset to open. This name can be
- * hierarchical.
+ * @param fileName The name of the asset to open. This name can be hierarchical.
*
* @see #open(String, int)
* @see #list
*/
- public final InputStream open(String fileName) throws IOException {
+ public @NonNull InputStream open(@NonNull String fileName) throws IOException {
return open(fileName, ACCESS_STREAMING);
}
@@ -353,8 +609,7 @@
* with an application as assets -- that is, files placed in to the
* "assets" directory.
*
- * @param fileName The name of the asset to open. This name can be
- * hierarchical.
+ * @param fileName The name of the asset to open. This name can be hierarchical.
* @param accessMode Desired access mode for retrieving the data.
*
* @see #ACCESS_UNKNOWN
@@ -364,34 +619,40 @@
* @see #open(String)
* @see #list
*/
- public final InputStream open(String fileName, int accessMode)
- throws IOException {
+ public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
+ ensureOpenLocked();
+ final long asset = nativeOpenAsset(mObject, fileName, accessMode);
+ if (asset == 0) {
+ throw new FileNotFoundException("Asset file: " + fileName);
}
- long asset = openAsset(fileName, accessMode);
- if (asset != 0) {
- AssetInputStream res = new AssetInputStream(asset);
- incRefsLocked(res.hashCode());
- return res;
- }
+ final AssetInputStream assetInputStream = new AssetInputStream(asset);
+ incRefsLocked(assetInputStream.hashCode());
+ return assetInputStream;
}
- throw new FileNotFoundException("Asset file: " + fileName);
}
- public final AssetFileDescriptor openFd(String fileName)
- throws IOException {
+ /**
+ * Open an uncompressed asset by mmapping it and returning an {@link AssetFileDescriptor}.
+ * This provides access to files that have been bundled with an application as assets -- that
+ * is, files placed in to the "assets" directory.
+ *
+ * The asset must be uncompressed, or an exception will be thrown.
+ *
+ * @param fileName The name of the asset to open. This name can be hierarchical.
+ * @return An open AssetFileDescriptor.
+ */
+ public @NonNull AssetFileDescriptor openFd(@NonNull String fileName) throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
+ ensureOpenLocked();
+ final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets);
+ if (pfd == null) {
+ throw new FileNotFoundException("Asset file: " + fileName);
}
- ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets);
- if (pfd != null) {
- return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
- }
+ return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
}
- throw new FileNotFoundException("Asset file: " + fileName);
}
/**
@@ -406,90 +667,121 @@
*
* @see #open
*/
- public native final String[] list(String path)
- throws IOException;
+ public @Nullable String[] list(@NonNull String path) throws IOException {
+ Preconditions.checkNotNull(path, "path");
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeList(mObject, path);
+ }
+ }
/**
- * {@hide}
* Open a non-asset file as an asset using ACCESS_STREAMING mode. This
* provides direct access to all of the files included in an application
* package (not only its assets). Applications should not normally use
* this.
- *
+ *
+ * @param fileName Name of the asset to retrieve.
+ *
* @see #open(String)
+ * @hide
*/
- public final InputStream openNonAsset(String fileName) throws IOException {
+ public @NonNull InputStream openNonAsset(@NonNull String fileName) throws IOException {
return openNonAsset(0, fileName, ACCESS_STREAMING);
}
/**
- * {@hide}
* Open a non-asset file as an asset using a specific access mode. This
* provides direct access to all of the files included in an application
* package (not only its assets). Applications should not normally use
* this.
- *
+ *
+ * @param fileName Name of the asset to retrieve.
+ * @param accessMode Desired access mode for retrieving the data.
+ *
+ * @see #ACCESS_UNKNOWN
+ * @see #ACCESS_STREAMING
+ * @see #ACCESS_RANDOM
+ * @see #ACCESS_BUFFER
* @see #open(String, int)
+ * @hide
*/
- public final InputStream openNonAsset(String fileName, int accessMode)
- throws IOException {
+ public @NonNull InputStream openNonAsset(@NonNull String fileName, int accessMode)
+ throws IOException {
return openNonAsset(0, fileName, accessMode);
}
/**
- * {@hide}
* Open a non-asset in a specified package. Not for use by applications.
- *
+ *
* @param cookie Identifier of the package to be opened.
* @param fileName Name of the asset to retrieve.
+ * @hide
*/
- public final InputStream openNonAsset(int cookie, String fileName)
- throws IOException {
+ public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName)
+ throws IOException {
return openNonAsset(cookie, fileName, ACCESS_STREAMING);
}
/**
- * {@hide}
* Open a non-asset in a specified package. Not for use by applications.
- *
+ *
* @param cookie Identifier of the package to be opened.
* @param fileName Name of the asset to retrieve.
* @param accessMode Desired access mode for retrieving the data.
+ * @hide
*/
- public final InputStream openNonAsset(int cookie, String fileName, int accessMode)
- throws IOException {
+ public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName, int accessMode)
+ throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
+ ensureOpenLocked();
+ final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode);
+ if (asset == 0) {
+ throw new FileNotFoundException("Asset absolute file: " + fileName);
}
- long asset = openNonAssetNative(cookie, fileName, accessMode);
- if (asset != 0) {
- AssetInputStream res = new AssetInputStream(asset);
- incRefsLocked(res.hashCode());
- return res;
- }
+ final AssetInputStream assetInputStream = new AssetInputStream(asset);
+ incRefsLocked(assetInputStream.hashCode());
+ return assetInputStream;
}
- throw new FileNotFoundException("Asset absolute file: " + fileName);
}
- public final AssetFileDescriptor openNonAssetFd(String fileName)
+ /**
+ * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}.
+ * This provides direct access to all of the files included in an application
+ * package (not only its assets). Applications should not normally use this.
+ *
+ * The asset must not be compressed, or an exception will be thrown.
+ *
+ * @param fileName Name of the asset to retrieve.
+ */
+ public @NonNull AssetFileDescriptor openNonAssetFd(@NonNull String fileName)
throws IOException {
return openNonAssetFd(0, fileName);
}
-
- public final AssetFileDescriptor openNonAssetFd(int cookie,
- String fileName) throws IOException {
+
+ /**
+ * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}.
+ * This provides direct access to all of the files included in an application
+ * package (not only its assets). Applications should not normally use this.
+ *
+ * The asset must not be compressed, or an exception will be thrown.
+ *
+ * @param cookie Identifier of the package to be opened.
+ * @param fileName Name of the asset to retrieve.
+ */
+ public @NonNull AssetFileDescriptor openNonAssetFd(int cookie, @NonNull String fileName)
+ throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
+ ensureOpenLocked();
+ final ParcelFileDescriptor pfd =
+ nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets);
+ if (pfd == null) {
+ throw new FileNotFoundException("Asset absolute file: " + fileName);
}
- ParcelFileDescriptor pfd = openNonAssetFdNative(cookie,
- fileName, mOffsets);
- if (pfd != null) {
- return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
- }
+ return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
}
- throw new FileNotFoundException("Asset absolute file: " + fileName);
}
/**
@@ -497,7 +789,7 @@
*
* @param fileName The name of the file to retrieve.
*/
- public final XmlResourceParser openXmlResourceParser(String fileName)
+ public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName)
throws IOException {
return openXmlResourceParser(0, fileName);
}
@@ -508,270 +800,265 @@
* @param cookie Identifier of the package to be opened.
* @param fileName The name of the file to retrieve.
*/
- public final XmlResourceParser openXmlResourceParser(int cookie,
- String fileName) throws IOException {
- XmlBlock block = openXmlBlockAsset(cookie, fileName);
- XmlResourceParser rp = block.newParser();
- block.close();
- return rp;
+ public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName)
+ throws IOException {
+ try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) {
+ XmlResourceParser parser = block.newParser();
+ // If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with
+ // a valid native pointer, which makes newParser always return non-null. But let's
+ // be paranoid.
+ if (parser == null) {
+ throw new AssertionError("block.newParser() returned a null parser");
+ }
+ return parser;
+ }
}
/**
- * {@hide}
- * Retrieve a non-asset as a compiled XML file. Not for use by
- * applications.
+ * Retrieve a non-asset as a compiled XML file. Not for use by applications.
*
* @param fileName The name of the file to retrieve.
+ * @hide
*/
- /*package*/ final XmlBlock openXmlBlockAsset(String fileName)
- throws IOException {
+ @NonNull XmlBlock openXmlBlockAsset(@NonNull String fileName) throws IOException {
return openXmlBlockAsset(0, fileName);
}
/**
- * {@hide}
* Retrieve a non-asset as a compiled XML file. Not for use by
* applications.
*
* @param cookie Identifier of the package to be opened.
* @param fileName Name of the asset to retrieve.
+ * @hide
*/
- /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName)
- throws IOException {
+ @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException {
+ Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
+ ensureOpenLocked();
+ final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
+ if (xmlBlock == 0) {
+ throw new FileNotFoundException("Asset XML file: " + fileName);
}
- long xmlBlock = openXmlAssetNative(cookie, fileName);
- if (xmlBlock != 0) {
- XmlBlock res = new XmlBlock(this, xmlBlock);
- incRefsLocked(res.hashCode());
- return res;
- }
+ final XmlBlock block = new XmlBlock(this, xmlBlock);
+ incRefsLocked(block.hashCode());
+ return block;
}
- throw new FileNotFoundException("Asset XML file: " + fileName);
}
- /*package*/ void xmlBlockGone(int id) {
+ void xmlBlockGone(int id) {
synchronized (this) {
decRefsLocked(id);
}
}
- /*package*/ final long createTheme() {
+ void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
+ @Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress,
+ long outIndicesAddress) {
+ Preconditions.checkNotNull(inAttrs, "inAttrs");
synchronized (this) {
- if (!mOpen) {
- throw new RuntimeException("Assetmanager has been closed");
- }
- long res = newTheme();
- incRefsLocked(res);
- return res;
+ // Need to synchronize on AssetManager because we will be accessing
+ // the native implementation of AssetManager.
+ ensureValidLocked();
+ nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes,
+ parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress,
+ outIndicesAddress);
}
}
- /*package*/ final void releaseTheme(long theme) {
+ boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
+ @Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues,
+ @NonNull int[] outIndices) {
+ Preconditions.checkNotNull(inAttrs, "inAttrs");
+ Preconditions.checkNotNull(outValues, "outValues");
+ Preconditions.checkNotNull(outIndices, "outIndices");
synchronized (this) {
- deleteTheme(theme);
- decRefsLocked(theme);
+ // Need to synchronize on AssetManager because we will be accessing
+ // the native implementation of AssetManager.
+ ensureValidLocked();
+ return nativeResolveAttrs(mObject,
+ themePtr, defStyleAttr, defStyleRes, inValues, inAttrs, outValues, outIndices);
}
}
+ boolean retrieveAttributes(@NonNull XmlBlock.Parser parser, @NonNull int[] inAttrs,
+ @NonNull int[] outValues, @NonNull int[] outIndices) {
+ Preconditions.checkNotNull(parser, "parser");
+ Preconditions.checkNotNull(inAttrs, "inAttrs");
+ Preconditions.checkNotNull(outValues, "outValues");
+ Preconditions.checkNotNull(outIndices, "outIndices");
+ synchronized (this) {
+ // Need to synchronize on AssetManager because we will be accessing
+ // the native implementation of AssetManager.
+ ensureValidLocked();
+ return nativeRetrieveAttributes(
+ mObject, parser.mParseState, inAttrs, outValues, outIndices);
+ }
+ }
+
+ long createTheme() {
+ synchronized (this) {
+ ensureValidLocked();
+ long themePtr = nativeThemeCreate(mObject);
+ incRefsLocked(themePtr);
+ return themePtr;
+ }
+ }
+
+ void releaseTheme(long themePtr) {
+ synchronized (this) {
+ nativeThemeDestroy(themePtr);
+ decRefsLocked(themePtr);
+ }
+ }
+
+ void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) {
+ synchronized (this) {
+ // Need to synchronize on AssetManager because we will be accessing
+ // the native implementation of AssetManager.
+ ensureValidLocked();
+ nativeThemeApplyStyle(mObject, themePtr, resId, force);
+ }
+ }
+
+ @Override
protected void finalize() throws Throwable {
- try {
- if (DEBUG_REFS && mNumRefs != 0) {
- Log.w(TAG, "AssetManager " + this
- + " finalized with non-zero refs: " + mNumRefs);
- if (mRefStacks != null) {
- for (RuntimeException e : mRefStacks.values()) {
- Log.w(TAG, "Reference from here", e);
- }
+ if (DEBUG_REFS && mNumRefs != 0) {
+ Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs);
+ if (mRefStacks != null) {
+ for (RuntimeException e : mRefStacks.values()) {
+ Log.w(TAG, "Reference from here", e);
}
}
- destroy();
- } finally {
- super.finalize();
+ }
+
+ if (mObject != 0) {
+ nativeDestroy(mObject);
}
}
-
+
+ /* No Locking is needed for AssetInputStream because an AssetInputStream is not-thread
+ safe and it does not rely on AssetManager once it has been created. It completely owns the
+ underlying Asset. */
public final class AssetInputStream extends InputStream {
+ private long mAssetNativePtr;
+ private long mLength;
+ private long mMarkPos;
+
/**
* @hide
*/
public final int getAssetInt() {
throw new UnsupportedOperationException();
}
+
/**
* @hide
*/
public final long getNativeAsset() {
- return mAsset;
+ return mAssetNativePtr;
}
- private AssetInputStream(long asset)
- {
- mAsset = asset;
- mLength = getAssetLength(asset);
+
+ private AssetInputStream(long assetNativePtr) {
+ mAssetNativePtr = assetNativePtr;
+ mLength = nativeAssetGetLength(assetNativePtr);
}
+
+ @Override
public final int read() throws IOException {
- return readAssetChar(mAsset);
+ ensureOpen();
+ return nativeAssetReadChar(mAssetNativePtr);
}
- public final boolean markSupported() {
- return true;
+
+ @Override
+ public final int read(@NonNull byte[] b) throws IOException {
+ ensureOpen();
+ Preconditions.checkNotNull(b, "b");
+ return nativeAssetRead(mAssetNativePtr, b, 0, b.length);
}
- public final int available() throws IOException {
- long len = getAssetRemainingLength(mAsset);
- return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len;
+
+ @Override
+ public final int read(@NonNull byte[] b, int off, int len) throws IOException {
+ ensureOpen();
+ Preconditions.checkNotNull(b, "b");
+ return nativeAssetRead(mAssetNativePtr, b, off, len);
}
- public final void close() throws IOException {
- synchronized (AssetManager.this) {
- if (mAsset != 0) {
- destroyAsset(mAsset);
- mAsset = 0;
- decRefsLocked(hashCode());
- }
- }
- }
- public final void mark(int readlimit) {
- mMarkPos = seekAsset(mAsset, 0, 0);
- }
- public final void reset() throws IOException {
- seekAsset(mAsset, mMarkPos, -1);
- }
- public final int read(byte[] b) throws IOException {
- return readAsset(mAsset, b, 0, b.length);
- }
- public final int read(byte[] b, int off, int len) throws IOException {
- return readAsset(mAsset, b, off, len);
- }
+
+ @Override
public final long skip(long n) throws IOException {
- long pos = seekAsset(mAsset, 0, 0);
- if ((pos+n) > mLength) {
- n = mLength-pos;
+ ensureOpen();
+ long pos = nativeAssetSeek(mAssetNativePtr, 0, 0);
+ if ((pos + n) > mLength) {
+ n = mLength - pos;
}
if (n > 0) {
- seekAsset(mAsset, n, 0);
+ nativeAssetSeek(mAssetNativePtr, n, 0);
}
return n;
}
- protected void finalize() throws Throwable
- {
+ @Override
+ public final int available() throws IOException {
+ ensureOpen();
+ final long len = nativeAssetGetRemainingLength(mAssetNativePtr);
+ return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) len;
+ }
+
+ @Override
+ public final boolean markSupported() {
+ return true;
+ }
+
+ @Override
+ public final void mark(int readlimit) {
+ ensureOpen();
+ mMarkPos = nativeAssetSeek(mAssetNativePtr, 0, 0);
+ }
+
+ @Override
+ public final void reset() throws IOException {
+ ensureOpen();
+ nativeAssetSeek(mAssetNativePtr, mMarkPos, -1);
+ }
+
+ @Override
+ public final void close() throws IOException {
+ if (mAssetNativePtr != 0) {
+ nativeAssetDestroy(mAssetNativePtr);
+ mAssetNativePtr = 0;
+
+ synchronized (AssetManager.this) {
+ decRefsLocked(hashCode());
+ }
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
close();
}
- private long mAsset;
- private long mLength;
- private long mMarkPos;
- }
-
- /**
- * Add an additional set of assets to the asset manager. This can be
- * either a directory or ZIP file. Not for use by applications. Returns
- * the cookie of the added asset, or 0 on failure.
- * {@hide}
- */
- public final int addAssetPath(String path) {
- return addAssetPathInternal(path, false);
- }
-
- /**
- * Add an application assets to the asset manager and loading it as shared library.
- * This can be either a directory or ZIP file. Not for use by applications. Returns
- * the cookie of the added asset, or 0 on failure.
- * {@hide}
- */
- public final int addAssetPathAsSharedLibrary(String path) {
- return addAssetPathInternal(path, true);
- }
-
- private final int addAssetPathInternal(String path, boolean appAsLib) {
- synchronized (this) {
- int res = addAssetPathNative(path, appAsLib);
- makeStringBlocks(mStringBlocks);
- return res;
+ private void ensureOpen() {
+ if (mAssetNativePtr == 0) {
+ throw new IllegalStateException("AssetInputStream is closed");
+ }
}
}
- private native final int addAssetPathNative(String path, boolean appAsLib);
-
- /**
- * Add an additional set of assets to the asset manager from an already open
- * FileDescriptor. Not for use by applications.
- * This does not give full AssetManager functionality for these assets,
- * since the origin of the file is not known for purposes of sharing,
- * overlay resolution, and other features. However it does allow you
- * to do simple access to the contents of the given fd as an apk file.
- * Performs a dup of the underlying fd, so you must take care of still closing
- * the FileDescriptor yourself (and can do that whenever you want).
- * Returns the cookie of the added asset, or 0 on failure.
- * {@hide}
- */
- public int addAssetFd(FileDescriptor fd, String debugPathName) {
- return addAssetFdInternal(fd, debugPathName, false);
- }
-
- private int addAssetFdInternal(FileDescriptor fd, String debugPathName,
- boolean appAsLib) {
- synchronized (this) {
- int res = addAssetFdNative(fd, debugPathName, appAsLib);
- makeStringBlocks(mStringBlocks);
- return res;
- }
- }
-
- private native int addAssetFdNative(FileDescriptor fd, String debugPathName,
- boolean appAsLib);
-
- /**
- * Add a set of assets to overlay an already added set of assets.
- *
- * This is only intended for application resources. System wide resources
- * are handled before any Java code is executed.
- *
- * {@hide}
- */
-
- public final int addOverlayPath(String idmapPath) {
- synchronized (this) {
- int res = addOverlayPathNative(idmapPath);
- makeStringBlocks(mStringBlocks);
- return res;
- }
- }
-
- /**
- * See addOverlayPath.
- *
- * {@hide}
- */
- public native final int addOverlayPathNative(String idmapPath);
-
- /**
- * Add multiple sets of assets to the asset manager at once. See
- * {@link #addAssetPath(String)} for more information. Returns array of
- * cookies for each added asset with 0 indicating failure, or null if
- * the input array of paths is null.
- * {@hide}
- */
- public final int[] addAssetPaths(String[] paths) {
- if (paths == null) {
- return null;
- }
-
- int[] cookies = new int[paths.length];
- for (int i = 0; i < paths.length; i++) {
- cookies[i] = addAssetPath(paths[i]);
- }
-
- return cookies;
- }
-
/**
* Determine whether the state in this asset manager is up-to-date with
* the files on the filesystem. If false is returned, you need to
* instantiate a new AssetManager class to see the new data.
- * {@hide}
+ * @hide
*/
- public native final boolean isUpToDate();
+ public boolean isUpToDate() {
+ for (ApkAssets apkAssets : getApkAssets()) {
+ if (!apkAssets.isUpToDate()) {
+ return false;
+ }
+ }
+ return true;
+ }
/**
* Get the locales that this asset manager contains data for.
@@ -784,7 +1071,12 @@
* are of the form {@code ll_CC} where {@code ll} is a two letter language code,
* and {@code CC} is a two letter country code.
*/
- public native final String[] getLocales();
+ public String[] getLocales() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetLocales(mObject, false /*excludeSystem*/);
+ }
+ }
/**
* Same as getLocales(), except that locales that are only provided by the system (i.e. those
@@ -794,131 +1086,57 @@
* assets support Cherokee and French, getLocales() would return
* [Cherokee, English, French, German], while getNonSystemLocales() would return
* [Cherokee, French].
- * {@hide}
+ * @hide
*/
- public native final String[] getNonSystemLocales();
-
- /** {@hide} */
- public native final Configuration[] getSizeConfigurations();
+ public String[] getNonSystemLocales() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetLocales(mObject, true /*excludeSystem*/);
+ }
+ }
/**
- * Change the configuation used when retrieving resources. Not for use by
+ * @hide
+ */
+ Configuration[] getSizeConfigurations() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetSizeConfigurations(mObject);
+ }
+ }
+
+ /**
+ * Change the configuration used when retrieving resources. Not for use by
* applications.
- * {@hide}
+ * @hide
*/
- public native final void setConfiguration(int mcc, int mnc, String locale,
- int orientation, int touchscreen, int density, int keyboard,
- int keyboardHidden, int navigation, int screenWidth, int screenHeight,
- int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
- int screenLayout, int uiMode, int colorMode, int majorVersion);
+ public void setConfiguration(int mcc, int mnc, @Nullable String locale, int orientation,
+ int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
+ int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
+ int screenHeightDp, int screenLayout, int uiMode, int colorMode, int majorVersion) {
+ synchronized (this) {
+ ensureValidLocked();
+ nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density,
+ keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
+ smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
+ colorMode, majorVersion);
+ }
+ }
/**
- * Retrieve the resource identifier for the given resource name.
+ * @hide
*/
- /*package*/ native final int getResourceIdentifier(String name,
- String defType,
- String defPackage);
+ public SparseArray<String> getAssignedPackageIdentifiers() {
+ synchronized (this) {
+ ensureValidLocked();
+ return nativeGetAssignedPackageIdentifiers(mObject);
+ }
+ }
- /*package*/ native final String getResourceName(int resid);
- /*package*/ native final String getResourcePackageName(int resid);
- /*package*/ native final String getResourceTypeName(int resid);
- /*package*/ native final String getResourceEntryName(int resid);
-
- private native final long openAsset(String fileName, int accessMode);
- private final native ParcelFileDescriptor openAssetFd(String fileName,
- long[] outOffsets) throws IOException;
- private native final long openNonAssetNative(int cookie, String fileName,
- int accessMode);
- private native ParcelFileDescriptor openNonAssetFdNative(int cookie,
- String fileName, long[] outOffsets) throws IOException;
- private native final void destroyAsset(long asset);
- private native final int readAssetChar(long asset);
- private native final int readAsset(long asset, byte[] b, int off, int len);
- private native final long seekAsset(long asset, long offset, int whence);
- private native final long getAssetLength(long asset);
- private native final long getAssetRemainingLength(long asset);
-
- /** Returns true if the resource was found, filling in mRetStringBlock and
- * mRetData. */
- private native final int loadResourceValue(int ident, short density, TypedValue outValue,
- boolean resolve);
- /** Returns true if the resource was found, filling in mRetStringBlock and
- * mRetData. */
- private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue,
- boolean resolve);
- /*package*/ static final int STYLE_NUM_ENTRIES = 6;
- /*package*/ static final int STYLE_TYPE = 0;
- /*package*/ static final int STYLE_DATA = 1;
- /*package*/ static final int STYLE_ASSET_COOKIE = 2;
- /*package*/ static final int STYLE_RESOURCE_ID = 3;
-
- /* Offset within typed data array for native changingConfigurations. */
- static final int STYLE_CHANGING_CONFIGURATIONS = 4;
-
- /*package*/ static final int STYLE_DENSITY = 5;
- /*package*/ native static final void applyStyle(long theme,
- int defStyleAttr, int defStyleRes, long xmlParser,
- int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress);
- /*package*/ native static final boolean resolveAttrs(long theme,
- int defStyleAttr, int defStyleRes, int[] inValues,
- int[] inAttrs, int[] outValues, int[] outIndices);
- /*package*/ native final boolean retrieveAttributes(
- long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices);
- /*package*/ native final int getArraySize(int resource);
- /*package*/ native final int retrieveArray(int resource, int[] outValues);
- private native final int getStringBlockCount();
- private native final long getNativeStringBlock(int block);
-
- /**
- * {@hide}
- */
- public native final String getCookieName(int cookie);
-
- /**
- * {@hide}
- */
- public native final SparseArray<String> getAssignedPackageIdentifiers();
-
- /**
- * {@hide}
- */
- public native static final int getGlobalAssetCount();
-
- /**
- * {@hide}
- */
- public native static final String getAssetAllocations();
-
- /**
- * {@hide}
- */
- public native static final int getGlobalAssetManagerCount();
-
- private native final long newTheme();
- private native final void deleteTheme(long theme);
- /*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force);
- /*package*/ native static final void copyTheme(long dest, long source);
- /*package*/ native static final void clearTheme(long theme);
- /*package*/ native static final int loadThemeAttributeValue(long theme, int ident,
- TypedValue outValue,
- boolean resolve);
- /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix);
- /*package*/ native static final @NativeConfig int getThemeChangingConfigurations(long theme);
-
- private native final long openXmlAssetNative(int cookie, String fileName);
-
- private native final String[] getArrayStringResource(int arrayRes);
- private native final int[] getArrayStringInfo(int arrayRes);
- /*package*/ native final int[] getArrayIntResource(int arrayRes);
- /*package*/ native final int[] getStyleAttributes(int themeRes);
-
- private native final void init(boolean isSystem);
- private native final void destroy();
-
- private final void incRefsLocked(long id) {
+ private void incRefsLocked(long id) {
if (DEBUG_REFS) {
if (mRefStacks == null) {
- mRefStacks = new HashMap<Long, RuntimeException>();
+ mRefStacks = new HashMap<>();
}
RuntimeException ex = new RuntimeException();
ex.fillInStackTrace();
@@ -926,16 +1144,117 @@
}
mNumRefs++;
}
-
- private final void decRefsLocked(long id) {
+
+ private void decRefsLocked(long id) {
if (DEBUG_REFS && mRefStacks != null) {
mRefStacks.remove(id);
}
mNumRefs--;
- //System.out.println("Dec streams: mNumRefs=" + mNumRefs
- // + " mReleased=" + mReleased);
- if (mNumRefs == 0) {
- destroy();
+ if (mNumRefs == 0 && mObject != 0) {
+ nativeDestroy(mObject);
+ mObject = 0;
}
}
+
+ // AssetManager setup native methods.
+ private static native long nativeCreate();
+ private static native void nativeDestroy(long ptr);
+ private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
+ boolean invalidateCaches);
+ private static native void nativeSetConfiguration(long ptr, int mcc, int mnc,
+ @Nullable String locale, int orientation, int touchscreen, int density, int keyboard,
+ int keyboardHidden, int navigation, int screenWidth, int screenHeight,
+ int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout,
+ int uiMode, int colorMode, int majorVersion);
+ private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
+ long ptr);
+
+ // File native methods.
+ private static native @Nullable String[] nativeList(long ptr, @NonNull String path)
+ throws IOException;
+ private static native long nativeOpenAsset(long ptr, @NonNull String fileName, int accessMode);
+ private static native @Nullable ParcelFileDescriptor nativeOpenAssetFd(long ptr,
+ @NonNull String fileName, long[] outOffsets) throws IOException;
+ private static native long nativeOpenNonAsset(long ptr, int cookie, @NonNull String fileName,
+ int accessMode);
+ private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie,
+ @NonNull String fileName, @NonNull long[] outOffsets) throws IOException;
+ private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName);
+
+ // Primitive resource native methods.
+ private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density,
+ @NonNull TypedValue outValue, boolean resolveReferences);
+ private static native int nativeGetResourceBagValue(long ptr, @AnyRes int resId, int bagEntryId,
+ @NonNull TypedValue outValue);
+
+ private static native @Nullable @AttrRes int[] nativeGetStyleAttributes(long ptr,
+ @StyleRes int resId);
+ private static native @Nullable String[] nativeGetResourceStringArray(long ptr,
+ @ArrayRes int resId);
+ private static native @Nullable int[] nativeGetResourceStringArrayInfo(long ptr,
+ @ArrayRes int resId);
+ private static native @Nullable int[] nativeGetResourceIntArray(long ptr, @ArrayRes int resId);
+ private static native int nativeGetResourceArraySize(long ptr, @ArrayRes int resId);
+ private static native int nativeGetResourceArray(long ptr, @ArrayRes int resId,
+ @NonNull int[] outValues);
+
+ // Resource name/ID native methods.
+ private static native @AnyRes int nativeGetResourceIdentifier(long ptr, @NonNull String name,
+ @Nullable String defType, @Nullable String defPackage);
+ private static native @Nullable String nativeGetResourceName(long ptr, @AnyRes int resid);
+ private static native @Nullable String nativeGetResourcePackageName(long ptr,
+ @AnyRes int resid);
+ private static native @Nullable String nativeGetResourceTypeName(long ptr, @AnyRes int resid);
+ private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid);
+ private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem);
+ private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr);
+
+ // Style attribute retrieval native methods.
+ private static native void nativeApplyStyle(long ptr, long themePtr, @AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes, long xmlParserPtr, @NonNull int[] inAttrs,
+ long outValuesAddress, long outIndicesAddress);
+ private static native boolean nativeResolveAttrs(long ptr, long themePtr,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes, @Nullable int[] inValues,
+ @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices);
+ private static native boolean nativeRetrieveAttributes(long ptr, long xmlParserPtr,
+ @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices);
+
+ // Theme related native methods
+ private static native long nativeThemeCreate(long ptr);
+ private static native void nativeThemeDestroy(long themePtr);
+ private static native void nativeThemeApplyStyle(long ptr, long themePtr, @StyleRes int resId,
+ boolean force);
+ static native void nativeThemeCopy(long destThemePtr, long sourceThemePtr);
+ static native void nativeThemeClear(long themePtr);
+ private static native int nativeThemeGetAttributeValue(long ptr, long themePtr,
+ @AttrRes int resId, @NonNull TypedValue outValue, boolean resolve);
+ private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag,
+ String prefix);
+ static native @NativeConfig int nativeThemeGetChangingConfigurations(long themePtr);
+
+ // AssetInputStream related native methods.
+ private static native void nativeAssetDestroy(long assetPtr);
+ private static native int nativeAssetReadChar(long assetPtr);
+ private static native int nativeAssetRead(long assetPtr, byte[] b, int off, int len);
+ private static native long nativeAssetSeek(long assetPtr, long offset, int whence);
+ private static native long nativeAssetGetLength(long assetPtr);
+ private static native long nativeAssetGetRemainingLength(long assetPtr);
+
+ private static native void nativeVerifySystemIdmaps();
+
+ // Global debug native methods.
+ /**
+ * @hide
+ */
+ public static native int getGlobalAssetCount();
+
+ /**
+ * @hide
+ */
+ public static native String getAssetAllocations();
+
+ /**
+ * @hide
+ */
+ public static native int getGlobalAssetManagerCount();
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index e173653c..8f58891 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -590,7 +590,7 @@
*/
@NonNull
public int[] getIntArray(@ArrayRes int id) throws NotFoundException {
- int[] res = mResourcesImpl.getAssets().getArrayIntResource(id);
+ int[] res = mResourcesImpl.getAssets().getResourceIntArray(id);
if (res != null) {
return res;
}
@@ -613,13 +613,13 @@
@NonNull
public TypedArray obtainTypedArray(@ArrayRes int id) throws NotFoundException {
final ResourcesImpl impl = mResourcesImpl;
- int len = impl.getAssets().getArraySize(id);
+ int len = impl.getAssets().getResourceArraySize(id);
if (len < 0) {
throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id));
}
TypedArray array = TypedArray.obtain(this, len);
- array.mLength = impl.getAssets().retrieveArray(id, array.mData);
+ array.mLength = impl.getAssets().getResourceArray(id, array.mData);
array.mIndices[0] = 0;
return array;
@@ -1789,8 +1789,7 @@
// out the attributes from the XML file (applying type information
// contained in the resources and such).
XmlBlock.Parser parser = (XmlBlock.Parser)set;
- mResourcesImpl.getAssets().retrieveAttributes(parser.mParseState, attrs,
- array.mData, array.mIndices);
+ mResourcesImpl.getAssets().retrieveAttributes(parser, attrs, array.mData, array.mIndices);
array.mXml = parser;
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 97cb78b..2a4b278 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -168,7 +168,6 @@
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
- mAssets.ensureStringBlocks();
}
public DisplayAdjustments getDisplayAdjustments() {
@@ -1274,8 +1273,7 @@
void applyStyle(int resId, boolean force) {
synchronized (mKey) {
- AssetManager.applyThemeStyle(mTheme, resId, force);
-
+ mAssets.applyStyleToTheme(mTheme, resId, force);
mThemeResId = resId;
mKey.append(resId, force);
}
@@ -1284,7 +1282,7 @@
void setTo(ThemeImpl other) {
synchronized (mKey) {
synchronized (other.mKey) {
- AssetManager.copyTheme(mTheme, other.mTheme);
+ AssetManager.nativeThemeCopy(mTheme, other.mTheme);
mThemeResId = other.mThemeResId;
mKey.setTo(other.getKey());
@@ -1307,12 +1305,10 @@
// out the attributes from the XML file (applying type information
// contained in the resources and such).
final XmlBlock.Parser parser = (XmlBlock.Parser) set;
- AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
- parser != null ? parser.mParseState : 0,
- attrs, attrs.length, array.mDataAddress, array.mIndicesAddress);
+ mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
+ array.mDataAddress, array.mIndicesAddress);
array.mTheme = wrapper;
array.mXml = parser;
-
return array;
}
}
@@ -1329,7 +1325,7 @@
}
final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
- AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
+ mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
array.mTheme = wrapper;
array.mXml = null;
return array;
@@ -1349,14 +1345,14 @@
@Config int getChangingConfigurations() {
synchronized (mKey) {
final @NativeConfig int nativeChangingConfig =
- AssetManager.getThemeChangingConfigurations(mTheme);
+ AssetManager.nativeThemeGetChangingConfigurations(mTheme);
return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
}
}
public void dump(int priority, String tag, String prefix) {
synchronized (mKey) {
- AssetManager.dumpTheme(mTheme, priority, tag, prefix);
+ mAssets.dumpTheme(mTheme, priority, tag, prefix);
}
}
@@ -1385,13 +1381,13 @@
*/
void rebase() {
synchronized (mKey) {
- AssetManager.clearTheme(mTheme);
+ AssetManager.nativeThemeClear(mTheme);
// Reapply the same styles in the same order.
for (int i = 0; i < mKey.mCount; i++) {
final int resId = mKey.mResId[i];
final boolean force = mKey.mForce[i];
- AssetManager.applyThemeStyle(mTheme, resId, force);
+ mAssets.applyStyleToTheme(mTheme, resId, force);
}
}
}
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index f33c751..cbb3c6d 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -61,6 +61,15 @@
return attrs;
}
+ // STYLE_ prefixed constants are offsets within the typed data array.
+ static final int STYLE_NUM_ENTRIES = 6;
+ static final int STYLE_TYPE = 0;
+ static final int STYLE_DATA = 1;
+ static final int STYLE_ASSET_COOKIE = 2;
+ static final int STYLE_RESOURCE_ID = 3;
+ static final int STYLE_CHANGING_CONFIGURATIONS = 4;
+ static final int STYLE_DENSITY = 5;
+
private final Resources mResources;
private DisplayMetrics mMetrics;
private AssetManager mAssets;
@@ -78,7 +87,7 @@
private void resize(int len) {
mLength = len;
- final int dataLen = len * AssetManager.STYLE_NUM_ENTRIES;
+ final int dataLen = len * STYLE_NUM_ENTRIES;
final int indicesLen = len + 1;
final VMRuntime runtime = VMRuntime.getRuntime();
if (mDataAddress == 0 || mData.length < dataLen) {
@@ -166,9 +175,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return null;
} else if (type == TypedValue.TYPE_STRING) {
@@ -203,9 +212,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return null;
} else if (type == TypedValue.TYPE_STRING) {
@@ -242,14 +251,13 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_STRING) {
- final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+ final int cookie = data[index + STYLE_ASSET_COOKIE];
if (cookie < 0) {
- return mXml.getPooledString(
- data[index+AssetManager.STYLE_DATA]).toString();
+ return mXml.getPooledString(data[index + STYLE_DATA]).toString();
}
}
return null;
@@ -274,11 +282,11 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava(
- data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
+ data[index + STYLE_CHANGING_CONFIGURATIONS]);
if ((changingConfigs & ~allowedChangingConfigs) != 0) {
return null;
}
@@ -320,14 +328,14 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA] != 0;
+ return data[index + STYLE_DATA] != 0;
}
final TypedValue v = mValue;
@@ -359,14 +367,14 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
}
final TypedValue v = mValue;
@@ -396,16 +404,16 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_FLOAT) {
- return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]);
+ return Float.intBitsToFloat(data[index + STYLE_DATA]);
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
}
final TypedValue v = mValue;
@@ -446,15 +454,15 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_STRING) {
final TypedValue value = mValue;
if (getValueAt(index, value)) {
@@ -498,7 +506,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
@@ -533,7 +541,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
@@ -564,15 +572,15 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -612,15 +620,14 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimension(
- data[index + AssetManager.STYLE_DATA], mMetrics);
+ return TypedValue.complexToDimension(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -661,15 +668,14 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelOffset(
- data[index + AssetManager.STYLE_DATA], mMetrics);
+ return TypedValue.complexToDimensionPixelOffset(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -711,15 +717,14 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelSize(
- data[index+AssetManager.STYLE_DATA], mMetrics);
+ return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -755,16 +760,15 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelSize(
- data[index+AssetManager.STYLE_DATA], mMetrics);
+ return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -795,15 +799,14 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
- return data[index+AssetManager.STYLE_DATA];
+ return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
- return TypedValue.complexToDimensionPixelSize(
- data[index + AssetManager.STYLE_DATA], mMetrics);
+ return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
}
return defValue;
@@ -833,15 +836,14 @@
}
final int attrIndex = index;
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_FRACTION) {
- return TypedValue.complexToFraction(
- data[index+AssetManager.STYLE_DATA], base, pbase);
+ return TypedValue.complexToFraction(data[index + STYLE_DATA], base, pbase);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
@@ -874,10 +876,10 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) {
- final int resid = data[index+AssetManager.STYLE_RESOURCE_ID];
+ if (data[index + STYLE_TYPE] != TypedValue.TYPE_NULL) {
+ final int resid = data[index + STYLE_RESOURCE_ID];
if (resid != 0) {
return resid;
}
@@ -902,10 +904,10 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
- return data[index + AssetManager.STYLE_DATA];
+ if (data[index + STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
+ return data[index + STYLE_DATA];
}
return defValue;
}
@@ -939,7 +941,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
@@ -975,7 +977,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
@@ -1006,7 +1008,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
return mResources.getTextArray(value.resourceId);
}
return null;
@@ -1027,7 +1029,7 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue);
+ return getValueAt(index * STYLE_NUM_ENTRIES, outValue);
}
/**
@@ -1043,8 +1045,8 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
- return mData[index + AssetManager.STYLE_TYPE];
+ index *= STYLE_NUM_ENTRIES;
+ return mData[index + STYLE_TYPE];
}
/**
@@ -1063,9 +1065,9 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
return type != TypedValue.TYPE_NULL;
}
@@ -1084,11 +1086,11 @@
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
- index *= AssetManager.STYLE_NUM_ENTRIES;
+ index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
return type != TypedValue.TYPE_NULL
- || data[index+AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
+ || data[index + STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
}
/**
@@ -1109,7 +1111,7 @@
}
final TypedValue value = mValue;
- if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+ if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
return value;
}
return null;
@@ -1181,16 +1183,16 @@
final int[] data = mData;
final int N = length();
for (int i = 0; i < N; i++) {
- final int index = i * AssetManager.STYLE_NUM_ENTRIES;
- if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
+ final int index = i * STYLE_NUM_ENTRIES;
+ if (data[index + STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
// Not an attribute, ignore.
continue;
}
// Null the entry so that we can safely call getZzz().
- data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL;
+ data[index + STYLE_TYPE] = TypedValue.TYPE_NULL;
- final int attr = data[index + AssetManager.STYLE_DATA];
+ final int attr = data[index + STYLE_DATA];
if (attr == 0) {
// Useless data, ignore.
continue;
@@ -1231,45 +1233,44 @@
final int[] data = mData;
final int N = length();
for (int i = 0; i < N; i++) {
- final int index = i * AssetManager.STYLE_NUM_ENTRIES;
- final int type = data[index + AssetManager.STYLE_TYPE];
+ final int index = i * STYLE_NUM_ENTRIES;
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
continue;
}
changingConfig |= ActivityInfo.activityInfoConfigNativeToJava(
- data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
+ data[index + STYLE_CHANGING_CONFIGURATIONS]);
}
return changingConfig;
}
private boolean getValueAt(int index, TypedValue outValue) {
final int[] data = mData;
- final int type = data[index+AssetManager.STYLE_TYPE];
+ final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return false;
}
outValue.type = type;
- outValue.data = data[index+AssetManager.STYLE_DATA];
- outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
- outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID];
+ outValue.data = data[index + STYLE_DATA];
+ outValue.assetCookie = data[index + STYLE_ASSET_COOKIE];
+ outValue.resourceId = data[index + STYLE_RESOURCE_ID];
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
- data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
- outValue.density = data[index+AssetManager.STYLE_DENSITY];
+ data[index + STYLE_CHANGING_CONFIGURATIONS]);
+ outValue.density = data[index + STYLE_DENSITY];
outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;
return true;
}
private CharSequence loadStringValueAt(int index) {
final int[] data = mData;
- final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+ final int cookie = data[index + STYLE_ASSET_COOKIE];
if (cookie < 0) {
if (mXml != null) {
- return mXml.getPooledString(
- data[index+AssetManager.STYLE_DATA]);
+ return mXml.getPooledString(data[index + STYLE_DATA]);
}
return null;
}
- return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]);
+ return mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]);
}
/** @hide */
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index e6b95741..d4ccffb 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -16,6 +16,7 @@
package android.content.res;
+import android.annotation.Nullable;
import android.util.TypedValue;
import com.android.internal.util.XmlUtils;
@@ -33,7 +34,7 @@
*
* {@hide}
*/
-final class XmlBlock {
+final class XmlBlock implements AutoCloseable {
private static final boolean DEBUG=false;
public XmlBlock(byte[] data) {
@@ -48,6 +49,7 @@
mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
}
+ @Override
public void close() {
synchronized (this) {
if (mOpen) {
@@ -478,13 +480,13 @@
* are doing! The given native object must exist for the entire lifetime
* of this newly creating XmlBlock.
*/
- XmlBlock(AssetManager assets, long xmlBlock) {
+ XmlBlock(@Nullable AssetManager assets, long xmlBlock) {
mAssets = assets;
mNative = xmlBlock;
mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
}
- private final AssetManager mAssets;
+ private @Nullable final AssetManager mAssets;
private final long mNative;
/*package*/ final StringBlock mStrings;
private boolean mOpen = true;
diff --git a/core/java/android/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index 657745c..bae2d04 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -17,11 +17,14 @@
package android.hardware.camera2;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
/**
@@ -44,6 +47,12 @@
* as {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE}). Refer to each key documentation on
* a case-by-case basis.</p>
*
+ * <p>For a logical multi-camera device, if the CaptureRequest contains a surface for an underlying
+ * physical camera, the corresponding {@link TotalCaptureResult} object will include the metadata
+ * for that physical camera. And its keys and values can be accessed by
+ * {@link #getPhysicalCameraKey}. If all requested surfaces are for the logical camera, no
+ * metadata for physical camera will be included.</p>
+ *
* <p>{@link TotalCaptureResult} objects are immutable.</p>
*
* @see CameraDevice.CaptureCallback#onCaptureCompleted
@@ -52,6 +61,8 @@
private final List<CaptureResult> mPartialResults;
private final int mSessionId;
+ // The map between physical camera id and capture result
+ private final HashMap<String, CameraMetadataNative> mPhysicalCaptureResults;
/**
* Takes ownership of the passed-in camera metadata and the partial results
@@ -60,7 +71,8 @@
* @hide
*/
public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent,
- CaptureResultExtras extras, List<CaptureResult> partials, int sessionId) {
+ CaptureResultExtras extras, List<CaptureResult> partials, int sessionId,
+ PhysicalCaptureResultInfo physicalResults[]) {
super(results, parent, extras);
if (partials == null) {
@@ -70,6 +82,12 @@
}
mSessionId = sessionId;
+
+ mPhysicalCaptureResults = new HashMap<String, CameraMetadataNative>();
+ for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) {
+ mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(),
+ onePhysicalResult.getCameraMetadata());
+ }
}
/**
@@ -83,6 +101,7 @@
mPartialResults = new ArrayList<>();
mSessionId = CameraCaptureSession.SESSION_ID_NONE;
+ mPhysicalCaptureResults = new HashMap<String, CameraMetadataNative>();
}
/**
@@ -111,4 +130,38 @@
public int getSessionId() {
return mSessionId;
}
+
+ /**
+ * Get a capture result field value for a particular physical camera id.
+ *
+ * <p>The field definitions can be found in {@link CaptureResult}.</p>
+ *
+ * <p>This function can be called for logical camera devices, which are devices that have
+ * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to {@link
+ * CameraCharacteristics#getPhysicalCameraIds} return a non-empty list of physical devices that
+ * are backing the logical camera. The camera id included in physicalCameraId argument
+ * selects an individual physical device, and returns its specific capture result field.</p>
+ *
+ * <p>This function should only be called if one or more streams from the underlying
+ * 'physicalCameraId' was requested by the corresponding capture request.</p>
+ *
+ * @throws IllegalArgumentException if the key was not valid, or the physicalCameraId is not
+ * applicable to the current camera, or a stream from 'physicalCameraId' is not requested by the
+ * corresponding capture request.
+ *
+ * @param key The result field to read.
+ * @param physicalCameraId The physical camera the result originates from.
+ *
+ * @return The value of that key, or {@code null} if the field is not set.
+ */
+ @Nullable
+ public <T> T getPhysicalCameraKey(Key<T> key, @NonNull String physicalCameraId) {
+ if (!mPhysicalCaptureResults.containsKey(physicalCameraId)) {
+ throw new IllegalArgumentException(
+ "No TotalCaptureResult exists for physical camera " + physicalCameraId);
+ }
+
+ CameraMetadataNative physicalMetadata = mPhysicalCaptureResults.get(physicalCameraId);
+ return physicalMetadata.get(key);
+ }
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index cab9d70..511fa43 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1001,19 +1001,17 @@
throw new IllegalArgumentException("Null Surface targets are not allowed");
}
- if (!request.isReprocess()) {
- continue;
- }
for (int i = 0; i < mConfiguredOutputs.size(); i++) {
OutputConfiguration configuration = mConfiguredOutputs.valueAt(i);
if (configuration.isForPhysicalCamera()
&& configuration.getSurfaces().contains(surface)) {
- throw new IllegalArgumentException(
- "Reprocess request on physical stream is not allowed");
+ if (request.isReprocess()) {
+ throw new IllegalArgumentException(
+ "Reprocess request on physical stream is not allowed");
+ }
}
}
}
-
}
synchronized(mInterfaceLock) {
@@ -1966,7 +1964,8 @@
@Override
public void onResultReceived(CameraMetadataNative result,
- CaptureResultExtras resultExtras) throws RemoteException {
+ CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
+ throws RemoteException {
int requestId = resultExtras.getRequestId();
long frameNumber = resultExtras.getFrameNumber();
@@ -2073,7 +2072,8 @@
request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
final int subsequenceId = resultExtras.getSubsequenceId();
final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
- request, resultExtras, partialResults, holder.getSessionId());
+ request, resultExtras, partialResults, holder.getSessionId(),
+ physicalResults);
// Final capture result
resultDispatch = new Runnable() {
@Override
@@ -2088,9 +2088,11 @@
NANO_PER_SECOND/fpsRange.getUpper());
CameraMetadataNative resultLocal =
new CameraMetadataNative(resultCopy);
+ // No logical multi-camera support for batched output mode.
TotalCaptureResult resultInBatch = new TotalCaptureResult(
resultLocal, holder.getRequest(i), resultExtras,
- partialResults, holder.getSessionId());
+ partialResults, holder.getSessionId(),
+ new PhysicalCaptureResultInfo[0]);
holder.getCallback().onCaptureCompleted(
CameraDeviceImpl.this,
diff --git a/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java b/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java
new file mode 100644
index 0000000..30eaf13
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 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.hardware.camera2.impl;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public class PhysicalCaptureResultInfo implements Parcelable {
+ private String cameraId;
+ private CameraMetadataNative cameraMetadata;
+
+ public static final Parcelable.Creator<PhysicalCaptureResultInfo> CREATOR =
+ new Parcelable.Creator<PhysicalCaptureResultInfo>() {
+ @Override
+ public PhysicalCaptureResultInfo createFromParcel(Parcel in) {
+ return new PhysicalCaptureResultInfo(in);
+ }
+
+ @Override
+ public PhysicalCaptureResultInfo[] newArray(int size) {
+ return new PhysicalCaptureResultInfo[size];
+ }
+ };
+
+ private PhysicalCaptureResultInfo(Parcel in) {
+ readFromParcel(in);
+ }
+
+ public PhysicalCaptureResultInfo(String cameraId, CameraMetadataNative cameraMetadata) {
+ this.cameraId = cameraId;
+ this.cameraMetadata = cameraMetadata;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(cameraId);
+ cameraMetadata.writeToParcel(dest, flags);
+ }
+
+ public void readFromParcel(Parcel in) {
+ cameraId = in.readString();
+ cameraMetadata = new CameraMetadataNative();
+ cameraMetadata.readFromParcel(in);
+ }
+
+ public String getCameraId() {
+ return cameraId;
+ }
+
+ public CameraMetadataNative getCameraMetadata() {
+ return cameraMetadata;
+ }
+}
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index eccab75..bc7b126 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -26,6 +26,7 @@
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.utils.SubmitInfo;
import android.os.ConditionVariable;
@@ -249,7 +250,8 @@
@Override
public void onResultReceived(final CameraMetadataNative result,
- final CaptureResultExtras resultExtras) {
+ final CaptureResultExtras resultExtras,
+ PhysicalCaptureResultInfo physicalResults[]) {
Object[] resultArray = new Object[] { result, resultExtras };
Message msg = getHandler().obtainMessage(RESULT_RECEIVED,
/*obj*/ resultArray);
@@ -320,7 +322,8 @@
Object[] resultArray = (Object[]) msg.obj;
CameraMetadataNative result = (CameraMetadataNative) resultArray[0];
CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1];
- mCallbacks.onResultReceived(result, resultExtras);
+ mCallbacks.onResultReceived(result, resultExtras,
+ new PhysicalCaptureResultInfo[0]);
break;
}
case PREPARED: {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index cb59fd1..e7f2134 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -23,6 +23,7 @@
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.impl.CameraDeviceImpl;
import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.utils.ArrayUtils;
@@ -253,7 +254,8 @@
holder.getRequestId());
}
try {
- mDeviceCallbacks.onResultReceived(result, extras);
+ mDeviceCallbacks.onResultReceived(result, extras,
+ new PhysicalCaptureResultInfo[0]);
} catch (RemoteException e) {
throw new IllegalStateException(
"Received remote exception during onCameraError callback: ", e);
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 36673cd..4de4880 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -656,6 +656,34 @@
}
/**
+ * Temporarily sets the brightness of the display.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission.
+ * </p>
+ *
+ * @param brightness The brightness value from 0 to 255.
+ *
+ * @hide Requires signature permission.
+ */
+ public void setTemporaryBrightness(int brightness) {
+ mGlobal.setTemporaryBrightness(brightness);
+ }
+
+ /**
+ * Temporarily sets the auto brightness adjustment factor.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission.
+ * </p>
+ *
+ * @param adjustment The adjustment factor from -1.0 to 1.0.
+ *
+ * @hide Requires signature permission.
+ */
+ public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
+ mGlobal.setTemporaryAutoBrightnessAdjustment(adjustment);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 9c851f1..2d5f5e0 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -489,6 +489,42 @@
}
}
+ /**
+ * Temporarily sets the brightness of the display.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission.
+ * </p>
+ *
+ * @param brightness The brightness value from 0 to 255.
+ *
+ * @hide Requires signature permission.
+ */
+ public void setTemporaryBrightness(int brightness) {
+ try {
+ mDm.setTemporaryBrightness(brightness);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Temporarily sets the auto brightness adjustment factor.
+ * <p>
+ * Requires the {@link android.Manifest.permission#CONTROL_DISPLAY_BRIGHTNESS} permission.
+ * </p>
+ *
+ * @param adjustment The adjustment factor from -1.0 to 1.0.
+ *
+ * @hide Requires signature permission.
+ */
+ public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
+ try {
+ mDm.setTemporaryAutoBrightnessAdjustment(adjustment);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, int event) {
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 078958a..1cfad4f 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -214,23 +214,12 @@
// nearby, turning it off temporarily until the object is moved away.
public boolean useProximitySensor;
- // The desired screen brightness in the range 0 (minimum / off) to 255 (brightest).
- // The display power controller may choose to clamp the brightness.
- // When auto-brightness is enabled, this field should specify a nominal default
- // value to use while waiting for the light sensor to report enough data.
- public int screenBrightness;
+ // An override of the screen brightness. Set to -1 is used if there's no override.
+ public int screenBrightnessOverride;
- // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter).
- public float screenAutoBrightnessAdjustment;
-
- // Set to true if screenBrightness and screenAutoBrightnessAdjustment were both
- // set by the user as opposed to being programmatically controlled by apps.
- public boolean brightnessSetByUser;
-
- // Set to true if screenBrightness or screenAutoBrightnessAdjustment are being set
- // temporarily. This is typically set while the user has their finger on the brightness
- // control, before they've selected the final brightness value.
- public boolean brightnessIsTemporary;
+ // An override of the screen auto-brightness adjustment factor in the range -1 (dimmer) to
+ // 1 (brighter). Set to Float.NaN if there's no override.
+ public float screenAutoBrightnessAdjustmentOverride;
// If true, enables automatic brightness control.
public boolean useAutoBrightness;
@@ -262,10 +251,10 @@
public DisplayPowerRequest() {
policy = POLICY_BRIGHT;
useProximitySensor = false;
- screenBrightness = PowerManager.BRIGHTNESS_ON;
- screenAutoBrightnessAdjustment = 0.0f;
- screenLowPowerBrightnessFactor = 0.5f;
+ screenBrightnessOverride = -1;
useAutoBrightness = false;
+ screenAutoBrightnessAdjustmentOverride = Float.NaN;
+ screenLowPowerBrightnessFactor = 0.5f;
blockScreenOn = false;
dozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
dozeScreenState = Display.STATE_UNKNOWN;
@@ -286,12 +275,10 @@
public void copyFrom(DisplayPowerRequest other) {
policy = other.policy;
useProximitySensor = other.useProximitySensor;
- screenBrightness = other.screenBrightness;
- screenAutoBrightnessAdjustment = other.screenAutoBrightnessAdjustment;
- screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor;
- brightnessSetByUser = other.brightnessSetByUser;
- brightnessIsTemporary = other.brightnessIsTemporary;
+ screenBrightnessOverride = other.screenBrightnessOverride;
useAutoBrightness = other.useAutoBrightness;
+ screenAutoBrightnessAdjustmentOverride = other.screenAutoBrightnessAdjustmentOverride;
+ screenLowPowerBrightnessFactor = other.screenLowPowerBrightnessFactor;
blockScreenOn = other.blockScreenOn;
lowPowerMode = other.lowPowerMode;
boostScreenBrightness = other.boostScreenBrightness;
@@ -309,13 +296,12 @@
return other != null
&& policy == other.policy
&& useProximitySensor == other.useProximitySensor
- && screenBrightness == other.screenBrightness
- && screenAutoBrightnessAdjustment == other.screenAutoBrightnessAdjustment
+ && screenBrightnessOverride == other.screenBrightnessOverride
+ && useAutoBrightness == other.useAutoBrightness
+ && floatEquals(screenAutoBrightnessAdjustmentOverride,
+ other.screenAutoBrightnessAdjustmentOverride)
&& screenLowPowerBrightnessFactor
== other.screenLowPowerBrightnessFactor
- && brightnessSetByUser == other.brightnessSetByUser
- && brightnessIsTemporary == other.brightnessIsTemporary
- && useAutoBrightness == other.useAutoBrightness
&& blockScreenOn == other.blockScreenOn
&& lowPowerMode == other.lowPowerMode
&& boostScreenBrightness == other.boostScreenBrightness
@@ -323,6 +309,10 @@
&& dozeScreenState == other.dozeScreenState;
}
+ private boolean floatEquals(float f1, float f2) {
+ return f1 == f2 || Float.isNaN(f1) && Float.isNaN(f2);
+ }
+
@Override
public int hashCode() {
return 0; // don't care
@@ -332,12 +322,11 @@
public String toString() {
return "policy=" + policyToString(policy)
+ ", useProximitySensor=" + useProximitySensor
- + ", screenBrightness=" + screenBrightness
- + ", screenAutoBrightnessAdjustment=" + screenAutoBrightnessAdjustment
- + ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor
- + ", brightnessSetByUser=" + brightnessSetByUser
- + ", brightnessIsTemporary=" + brightnessIsTemporary
+ + ", screenBrightnessOverride=" + screenBrightnessOverride
+ ", useAutoBrightness=" + useAutoBrightness
+ + ", screenAutoBrightnessAdjustmentOverride="
+ + screenAutoBrightnessAdjustmentOverride
+ + ", screenLowPowerBrightnessFactor=" + screenLowPowerBrightnessFactor
+ ", blockScreenOn=" + blockScreenOn
+ ", lowPowerMode=" + lowPowerMode
+ ", boostScreenBrightness=" + boostScreenBrightness
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 5b7b32f..13599cf 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -92,4 +92,10 @@
// the same as the calling user.
void setBrightnessConfigurationForUser(in BrightnessConfiguration c, int userId,
String packageName);
+
+ // Temporarily sets the display brightness.
+ void setTemporaryBrightness(int brightness);
+
+ // Temporarily sets the auto brightness adjustment factor.
+ void setTemporaryAutoBrightnessAdjustment(float adjustment);
}
diff --git a/core/java/android/hardware/radio/ITuner.aidl b/core/java/android/hardware/radio/ITuner.aidl
index bf5e391..429f1f3 100644
--- a/core/java/android/hardware/radio/ITuner.aidl
+++ b/core/java/android/hardware/radio/ITuner.aidl
@@ -64,8 +64,6 @@
void cancelAnnouncement();
- RadioManager.ProgramInfo getProgramInformation();
-
Bitmap getImage(int id);
/**
@@ -92,6 +90,4 @@
* @return Vendor-specific key-value pairs, must be Map<String, String>
*/
Map getParameters(in List<String> keys);
-
- boolean isAntennaConnected();
}
diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl
index 54af30f..b32daa5 100644
--- a/core/java/android/hardware/radio/ITunerCallback.aidl
+++ b/core/java/android/hardware/radio/ITunerCallback.aidl
@@ -17,12 +17,14 @@
package android.hardware.radio;
import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
/** {@hide} */
oneway interface ITunerCallback {
void onError(int status);
+ void onTuneFailed(int result, in ProgramSelector selector);
void onConfigurationChanged(in RadioManager.BandConfig config);
void onCurrentProgramInfoChanged(in RadioManager.ProgramInfo info);
void onTrafficAnnouncement(boolean active);
diff --git a/core/java/android/hardware/radio/RadioTuner.java b/core/java/android/hardware/radio/RadioTuner.java
index ed20c4a..0edd055 100644
--- a/core/java/android/hardware/radio/RadioTuner.java
+++ b/core/java/android/hardware/radio/RadioTuner.java
@@ -64,7 +64,9 @@
* <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
* service fails, </li>
* </ul>
+ * @deprecated Only applicable for HAL 1.x.
*/
+ @Deprecated
public abstract int setConfiguration(RadioManager.BandConfig config);
/**
@@ -80,7 +82,10 @@
* <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
* service fails, </li>
* </ul>
+ *
+ * @deprecated Only applicable for HAL 1.x.
*/
+ @Deprecated
public abstract int getConfiguration(RadioManager.BandConfig[] config);
@@ -228,7 +233,9 @@
* <li>{@link RadioManager#STATUS_DEAD_OBJECT} if the binder transaction to the native
* service fails, </li>
* </ul>
+ * @deprecated Use {@link onProgramInfoChanged} callback instead.
*/
+ @Deprecated
public abstract int getProgramInformation(RadioManager.ProgramInfo[] info);
/**
@@ -427,7 +434,10 @@
* Get current antenna connection state for current configuration.
* Only valid if a configuration has been applied.
* @return {@code true} if the antenna is connected, {@code false} otherwise.
+ *
+ * @deprecated Use {@link onAntennaState} callback instead
*/
+ @Deprecated
public abstract boolean isAntennaConnected();
/**
@@ -446,20 +456,41 @@
public abstract boolean hasControl();
/** Indicates a failure of radio IC or driver.
- * The application must close and re open the tuner */
+ * The application must close and re open the tuner
+ * @deprecated See {@link onError} callback.
+ */
+ @Deprecated
public static final int ERROR_HARDWARE_FAILURE = 0;
/** Indicates a failure of the radio service.
- * The application must close and re open the tuner */
+ * The application must close and re open the tuner
+ * @deprecated See {@link onError} callback.
+ */
+ @Deprecated
public static final int ERROR_SERVER_DIED = 1;
- /** A pending seek or tune operation was cancelled */
+ /** A pending seek or tune operation was cancelled
+ * @deprecated See {@link onError} callback.
+ */
+ @Deprecated
public static final int ERROR_CANCELLED = 2;
- /** A pending seek or tune operation timed out */
+ /** A pending seek or tune operation timed out
+ * @deprecated See {@link onError} callback.
+ */
+ @Deprecated
public static final int ERROR_SCAN_TIMEOUT = 3;
- /** The requested configuration could not be applied */
+ /** The requested configuration could not be applied
+ * @deprecated See {@link onError} callback.
+ */
+ @Deprecated
public static final int ERROR_CONFIG = 4;
- /** Background scan was interrupted due to hardware becoming temporarily unavailable. */
+ /** Background scan was interrupted due to hardware becoming temporarily unavailable.
+ * @deprecated See {@link onError} callback.
+ */
+ @Deprecated
public static final int ERROR_BACKGROUND_SCAN_UNAVAILABLE = 5;
- /** Background scan failed due to other error, ie. HW failure. */
+ /** Background scan failed due to other error, ie. HW failure.
+ * @deprecated See {@link onError} callback.
+ */
+ @Deprecated
public static final int ERROR_BACKGROUND_SCAN_FAILED = 6;
/**
@@ -473,13 +504,29 @@
* status is one of {@link #ERROR_HARDWARE_FAILURE}, {@link #ERROR_SERVER_DIED},
* {@link #ERROR_CANCELLED}, {@link #ERROR_SCAN_TIMEOUT},
* {@link #ERROR_CONFIG}
+ *
+ * @deprecated Use {@link onTuneFailed} for tune, scan and step;
+ * other use cases (configuration, background scan) are already deprecated.
*/
public void onError(int status) {}
+
+ /**
+ * Called when tune, scan or step operation fails.
+ *
+ * @param result cause of the failure
+ * @param selector ProgramSelector argument of tune that failed;
+ * null for scan and step.
+ */
+ public void onTuneFailed(int result, @Nullable ProgramSelector selector) {}
+
/**
* onConfigurationChanged() is called upon successful completion of
* {@link RadioManager#openTuner(int, RadioManager.BandConfig, boolean, Callback, Handler)}
* or {@link RadioTuner#setConfiguration(RadioManager.BandConfig)}
+ *
+ * @deprecated Only applicable for HAL 1.x.
*/
+ @Deprecated
public void onConfigurationChanged(RadioManager.BandConfig config) {}
/**
diff --git a/core/java/android/hardware/radio/TunerAdapter.java b/core/java/android/hardware/radio/TunerAdapter.java
index 91944bf..85f3115 100644
--- a/core/java/android/hardware/radio/TunerAdapter.java
+++ b/core/java/android/hardware/radio/TunerAdapter.java
@@ -202,15 +202,17 @@
@Override
public int getProgramInformation(RadioManager.ProgramInfo[] info) {
if (info == null || info.length != 1) {
- throw new IllegalArgumentException("The argument must be an array of length 1");
+ Log.e(TAG, "The argument must be an array of length 1");
+ return RadioManager.STATUS_BAD_VALUE;
}
- try {
- info[0] = mTuner.getProgramInformation();
- return RadioManager.STATUS_OK;
- } catch (RemoteException e) {
- Log.e(TAG, "service died", e);
- return RadioManager.STATUS_DEAD_OBJECT;
+
+ RadioManager.ProgramInfo current = mCallback.getCurrentProgramInformation();
+ if (current == null) {
+ Log.w(TAG, "Didn't get program info yet");
+ return RadioManager.STATUS_INVALID_OPERATION;
}
+ info[0] = current;
+ return RadioManager.STATUS_OK;
}
@Override
@@ -288,12 +290,20 @@
@Override
public boolean isAnalogForced() {
- return isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG);
+ try {
+ return isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG);
+ } catch (UnsupportedOperationException ex) {
+ throw new IllegalStateException(ex);
+ }
}
@Override
public void setAnalogForced(boolean isForced) {
- setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, isForced);
+ try {
+ setConfigFlag(RadioManager.CONFIG_FORCE_ANALOG, isForced);
+ } catch (UnsupportedOperationException ex) {
+ throw new IllegalStateException(ex);
+ }
}
@Override
@@ -343,11 +353,7 @@
@Override
public boolean isAntennaConnected() {
- try {
- return mTuner.isAntennaConnected();
- } catch (RemoteException e) {
- throw new RuntimeException("service died", e);
- }
+ return mCallback.isAntennaConnected();
}
@Override
diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java
index b299ffe..7437c40 100644
--- a/core/java/android/hardware/radio/TunerCallbackAdapter.java
+++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java
@@ -37,8 +37,12 @@
@NonNull private final Handler mHandler;
@Nullable ProgramList mProgramList;
- @Nullable List<RadioManager.ProgramInfo> mLastCompleteList; // for legacy getProgramList call
+
+ // cache for deprecated methods
+ boolean mIsAntennaConnected = true;
+ @Nullable List<RadioManager.ProgramInfo> mLastCompleteList;
private boolean mDelayedCompleteCallback = false;
+ @Nullable RadioManager.ProgramInfo mCurrentProgramInfo;
TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) {
mCallback = callback;
@@ -92,12 +96,46 @@
}
}
+ @Nullable RadioManager.ProgramInfo getCurrentProgramInformation() {
+ synchronized (mLock) {
+ return mCurrentProgramInfo;
+ }
+ }
+
+ boolean isAntennaConnected() {
+ return mIsAntennaConnected;
+ }
+
@Override
public void onError(int status) {
mHandler.post(() -> mCallback.onError(status));
}
@Override
+ public void onTuneFailed(int status, @Nullable ProgramSelector selector) {
+ mHandler.post(() -> mCallback.onTuneFailed(status, selector));
+
+ int errorCode;
+ switch (status) {
+ case RadioManager.STATUS_PERMISSION_DENIED:
+ case RadioManager.STATUS_DEAD_OBJECT:
+ errorCode = RadioTuner.ERROR_SERVER_DIED;
+ break;
+ case RadioManager.STATUS_ERROR:
+ case RadioManager.STATUS_NO_INIT:
+ case RadioManager.STATUS_BAD_VALUE:
+ case RadioManager.STATUS_INVALID_OPERATION:
+ Log.i(TAG, "Got an error with no mapping to the legacy API (" + status
+ + "), doing a best-effort conversion to ERROR_SCAN_TIMEOUT");
+ // fall through
+ case RadioManager.STATUS_TIMED_OUT:
+ default:
+ errorCode = RadioTuner.ERROR_SCAN_TIMEOUT;
+ }
+ mHandler.post(() -> mCallback.onError(errorCode));
+ }
+
+ @Override
public void onConfigurationChanged(RadioManager.BandConfig config) {
mHandler.post(() -> mCallback.onConfigurationChanged(config));
}
@@ -109,6 +147,10 @@
return;
}
+ synchronized (mLock) {
+ mCurrentProgramInfo = info;
+ }
+
mHandler.post(() -> {
mCallback.onProgramInfoChanged(info);
@@ -129,6 +171,7 @@
@Override
public void onAntennaState(boolean connected) {
+ mIsAntennaConnected = connected;
mHandler.post(() -> mCallback.onAntennaState(connected));
}
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 37e2c4f..9ccdbe2 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -17,11 +17,14 @@
import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -128,13 +131,6 @@
int status = result.status;
checkResultStatus(status);
mResourceId = result.resourceId;
-
- /* Keepalive will silently fail if not needed by the config; but, if needed and
- * it fails to start, we need to bail because a transform will not be reliable
- * to use if keepalive is expected to offload and fails.
- */
- // FIXME: if keepalive fails, we need to fail spectacularly
- startKeepalive(mContext);
Log.d(TAG, "Added Transform with Id " + mResourceId);
mCloseGuard.open("build");
} catch (RemoteException e) {
@@ -164,13 +160,9 @@
return;
}
try {
- /* Order matters here because the keepalive is best-effort but could fail in some
- * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we
- * still want to clear out the transform.
- */
IIpSecService svc = getIpSecService();
svc.deleteTransform(mResourceId);
- stopKeepalive();
+ stopNattKeepalive();
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} finally {
@@ -198,42 +190,35 @@
private final Context mContext;
private final CloseGuard mCloseGuard = CloseGuard.get();
private ConnectivityManager.PacketKeepalive mKeepalive;
- private int mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
- private Object mKeepaliveSyncLock = new Object();
- private ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback =
+ private Handler mCallbackHandler;
+ private final ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback =
new ConnectivityManager.PacketKeepaliveCallback() {
@Override
public void onStarted() {
- synchronized (mKeepaliveSyncLock) {
- mKeepaliveStatus = ConnectivityManager.PacketKeepalive.SUCCESS;
- mKeepaliveSyncLock.notifyAll();
+ synchronized (this) {
+ mCallbackHandler.post(() -> mUserKeepaliveCallback.onStarted());
}
}
@Override
public void onStopped() {
- synchronized (mKeepaliveSyncLock) {
- mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
- mKeepaliveSyncLock.notifyAll();
+ synchronized (this) {
+ mKeepalive = null;
+ mCallbackHandler.post(() -> mUserKeepaliveCallback.onStopped());
}
}
@Override
public void onError(int error) {
- synchronized (mKeepaliveSyncLock) {
- mKeepaliveStatus = error;
- mKeepaliveSyncLock.notifyAll();
+ synchronized (this) {
+ mKeepalive = null;
+ mCallbackHandler.post(() -> mUserKeepaliveCallback.onError(error));
}
}
};
- /* Package */
- void startKeepalive(Context c) {
- if (mConfig.getNattKeepaliveInterval() != 0) {
- Log.wtf(TAG, "Keepalive not yet supported.");
- }
- }
+ private NattKeepaliveCallback mUserKeepaliveCallback;
/** @hide */
@VisibleForTesting
@@ -241,9 +226,93 @@
return mResourceId;
}
- /* Package */
- void stopKeepalive() {
- return;
+ /**
+ * A callback class to provide status information regarding a NAT-T keepalive session
+ *
+ * <p>Use this callback to receive status information regarding a NAT-T keepalive session
+ * by registering it when calling {@link #startNattKeepalive}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static class NattKeepaliveCallback {
+ /** The specified {@code Network} is not connected. */
+ public static final int ERROR_INVALID_NETWORK = 1;
+ /** The hardware does not support this request. */
+ public static final int ERROR_HARDWARE_UNSUPPORTED = 2;
+ /** The hardware returned an error. */
+ public static final int ERROR_HARDWARE_ERROR = 3;
+
+ /** The requested keepalive was successfully started. */
+ public void onStarted() {}
+ /** The keepalive was successfully stopped. */
+ public void onStopped() {}
+ /** An error occurred. */
+ public void onError(int error) {}
+ }
+
+ /**
+ * Start a NAT-T keepalive session for the current transform.
+ *
+ * For a transform that is using UDP encapsulated IPv4, NAT-T offloading provides
+ * a power efficient mechanism of sending NAT-T packets at a specified interval.
+ *
+ * @param userCallback a {@link #NattKeepaliveCallback} to receive asynchronous status
+ * information about the requested NAT-T keepalive session.
+ * @param intervalSeconds the interval between NAT-T keepalives being sent. The
+ * the allowed range is between 20 and 3600 seconds.
+ * @param handler a handler on which to post callbacks when received.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void startNattKeepalive(@NonNull NattKeepaliveCallback userCallback,
+ int intervalSeconds, @NonNull Handler handler) throws IOException {
+ checkNotNull(userCallback);
+ if (intervalSeconds < 20 || intervalSeconds > 3600) {
+ throw new IllegalArgumentException("Invalid NAT-T keepalive interval");
+ }
+ checkNotNull(handler);
+ if (mResourceId == INVALID_RESOURCE_ID) {
+ throw new IllegalStateException(
+ "Packet keepalive cannot be started for an inactive transform");
+ }
+
+ synchronized (mKeepaliveCallback) {
+ if (mKeepaliveCallback != null) {
+ throw new IllegalStateException("Keepalive already active");
+ }
+
+ mUserKeepaliveCallback = userCallback;
+ ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ mKeepalive = cm.startNattKeepalive(
+ mConfig.getNetwork(), intervalSeconds, mKeepaliveCallback,
+ NetworkUtils.numericToInetAddress(mConfig.getSourceAddress()),
+ 4500, // FIXME urgently, we need to get the port number from the Encap socket
+ NetworkUtils.numericToInetAddress(mConfig.getDestinationAddress()));
+ mCallbackHandler = handler;
+ }
+ }
+
+ /**
+ * Stop an ongoing NAT-T keepalive session.
+ *
+ * Calling this API will request that an ongoing NAT-T keepalive session be terminated.
+ * If this API is not called when a Transform is closed, the underlying NAT-T session will
+ * be terminated automatically.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void stopNattKeepalive() {
+ synchronized (mKeepaliveCallback) {
+ if (mKeepalive == null) {
+ Log.e(TAG, "No active keepalive to stop");
+ return;
+ }
+ mKeepalive.stop();
+ }
}
/** This class is used to build {@link IpSecTransform} objects. */
@@ -323,26 +392,6 @@
return this;
}
- // TODO: Decrease the minimum keepalive to maybe 10?
- // TODO: Probably a better exception to throw for NATTKeepalive failure
- // TODO: Specify the needed NATT keepalive permission.
- /**
- * Set NAT-T keepalives to be sent with a given interval.
- *
- * <p>This will set power-efficient keepalive packets to be sent by the system. If NAT-T
- * keepalive is requested but cannot be activated, then creation of an {@link
- * IpSecTransform} will fail when calling the build method.
- *
- * @param intervalSeconds the maximum number of seconds between keepalive packets. Must be
- * between 20s and 3600s.
- * @hide
- */
- @SystemApi
- public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) {
- mConfig.setNattKeepaliveInterval(intervalSeconds);
- return this;
- }
-
/**
* Build a transport mode {@link IpSecTransform}.
*
diff --git a/core/java/android/net/KeepalivePacketData.aidl b/core/java/android/net/KeepalivePacketData.aidl
new file mode 100644
index 0000000..d456b53
--- /dev/null
+++ b/core/java/android/net/KeepalivePacketData.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 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.net;
+
+parcelable KeepalivePacketData;
diff --git a/core/java/android/net/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java
new file mode 100644
index 0000000..08d4ff5
--- /dev/null
+++ b/core/java/android/net/KeepalivePacketData.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2015 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.net;
+
+import android.system.OsConstants;
+import android.net.ConnectivityManager;
+import android.net.util.IpUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.system.OsConstants;
+import android.util.Log;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import static android.net.ConnectivityManager.PacketKeepalive.*;
+
+/**
+ * Represents the actual packets that are sent by the
+ * {@link android.net.ConnectivityManager.PacketKeepalive} API.
+ *
+ * @hide
+ */
+public class KeepalivePacketData implements Parcelable {
+ private static final String TAG = "KeepalivePacketData";
+
+ /** Source IP address */
+ public final InetAddress srcAddress;
+
+ /** Destination IP address */
+ public final InetAddress dstAddress;
+
+ /** Source port */
+ public final int srcPort;
+
+ /** Destination port */
+ public final int dstPort;
+
+ /** Packet data. A raw byte string of packet data, not including the link-layer header. */
+ private final byte[] mPacket;
+
+ private static final int IPV4_HEADER_LENGTH = 20;
+ private static final int UDP_HEADER_LENGTH = 8;
+
+ // This should only be constructed via static factory methods, such as
+ // nattKeepalivePacket
+ protected KeepalivePacketData(InetAddress srcAddress, int srcPort,
+ InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException {
+ this.srcAddress = srcAddress;
+ this.dstAddress = dstAddress;
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ this.mPacket = data;
+
+ // Check we have two IP addresses of the same family.
+ if (srcAddress == null || dstAddress == null || !srcAddress.getClass().getName()
+ .equals(dstAddress.getClass().getName())) {
+ Log.e(TAG, "Invalid or mismatched InetAddresses in KeepalivePacketData");
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+
+ // Check the ports.
+ if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) {
+ Log.e(TAG, "Invalid ports in KeepalivePacketData");
+ throw new InvalidPacketException(ERROR_INVALID_PORT);
+ }
+ }
+
+ public static class InvalidPacketException extends Exception {
+ public final int error;
+ public InvalidPacketException(int error) {
+ this.error = error;
+ }
+ }
+
+ public byte[] getPacket() {
+ return mPacket.clone();
+ }
+
+ public static KeepalivePacketData nattKeepalivePacket(
+ InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort)
+ throws InvalidPacketException {
+
+ // FIXME: remove this and actually support IPv6 keepalives
+ if (srcAddress instanceof Inet6Address && dstAddress instanceof Inet6Address) {
+ // Optimistically returning an IPv6 Keepalive Packet with no data,
+ // which currently only works on cellular
+ return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, new byte[0]);
+ }
+
+ if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) {
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+
+ if (dstPort != NATT_PORT) {
+ throw new InvalidPacketException(ERROR_INVALID_PORT);
+ }
+
+ int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1;
+ ByteBuffer buf = ByteBuffer.allocate(length);
+ buf.order(ByteOrder.BIG_ENDIAN);
+ buf.putShort((short) 0x4500); // IP version and TOS
+ buf.putShort((short) length);
+ buf.putInt(0); // ID, flags, offset
+ buf.put((byte) 64); // TTL
+ buf.put((byte) OsConstants.IPPROTO_UDP);
+ int ipChecksumOffset = buf.position();
+ buf.putShort((short) 0); // IP checksum
+ buf.put(srcAddress.getAddress());
+ buf.put(dstAddress.getAddress());
+ buf.putShort((short) srcPort);
+ buf.putShort((short) dstPort);
+ buf.putShort((short) (length - 20)); // UDP length
+ int udpChecksumOffset = buf.position();
+ buf.putShort((short) 0); // UDP checksum
+ buf.put((byte) 0xff); // NAT-T keepalive
+ buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
+ buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH));
+
+ return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
+ }
+
+ /* Parcelable Implementation */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Write to parcel */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(srcAddress.getHostAddress());
+ out.writeString(dstAddress.getHostAddress());
+ out.writeInt(srcPort);
+ out.writeInt(dstPort);
+ out.writeByteArray(mPacket);
+ }
+
+ private KeepalivePacketData(Parcel in) {
+ srcAddress = NetworkUtils.numericToInetAddress(in.readString());
+ dstAddress = NetworkUtils.numericToInetAddress(in.readString());
+ srcPort = in.readInt();
+ dstPort = in.readInt();
+ mPacket = in.createByteArray();
+ }
+
+ /** Parcelable Creator */
+ public static final Parcelable.Creator<KeepalivePacketData> CREATOR =
+ new Parcelable.Creator<KeepalivePacketData>() {
+ public KeepalivePacketData createFromParcel(Parcel in) {
+ return new KeepalivePacketData(in);
+ }
+
+ public KeepalivePacketData[] newArray(int size) {
+ return new KeepalivePacketData[size];
+ }
+ };
+
+}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 1a4765b..8e05cfa 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -892,7 +892,7 @@
*
* @hide
*/
- private Set<UidRange> mUids = null;
+ private ArraySet<UidRange> mUids = null;
/**
* Convenience method to set the UIDs this network applies to to a single UID.
@@ -1178,7 +1178,7 @@
dest.writeInt(mLinkDownBandwidthKbps);
dest.writeParcelable((Parcelable) mNetworkSpecifier, flags);
dest.writeInt(mSignalStrength);
- dest.writeArraySet(new ArraySet<>(mUids));
+ dest.writeArraySet(mUids);
}
public static final Creator<NetworkCapabilities> CREATOR =
diff --git a/services/net/java/android/net/util/IpUtils.java b/core/java/android/net/util/IpUtils.java
similarity index 100%
rename from services/net/java/android/net/util/IpUtils.java
rename to core/java/android/net/util/IpUtils.java
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 62731e8..158041d 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -302,9 +302,8 @@
}
/** {@hide} */
- public static File getProfileSnapshotPath(String packageName, String codePath) {
- return buildPath(buildPath(getDataDirectory(), "misc", "profiles", "ref", packageName,
- "primary.prof.snapshot"));
+ public static File getDataRefProfilesDePackageDirectory(String packageName) {
+ return buildPath(getDataDirectory(), "misc", "profiles", "ref", packageName);
}
/** {@hide} */
diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java
index 4d7d931..335bf9d 100644
--- a/core/java/android/os/HidlSupport.java
+++ b/core/java/android/os/HidlSupport.java
@@ -16,6 +16,8 @@
package android.os;
+import android.annotation.SystemApi;
+
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
@@ -25,6 +27,7 @@
import java.util.stream.IntStream;
/** @hide */
+@SystemApi
public class HidlSupport {
/**
* Similar to Objects.deepEquals, but also take care of lists.
@@ -36,7 +39,9 @@
* 2.3 Both are Lists, elements are checked recursively
* 2.4 (If both are collections other than lists or maps, throw an error)
* 2.5 lft.equals(rgt) returns true
+ * @hide
*/
+ @SystemApi
public static boolean deepEquals(Object lft, Object rgt) {
if (lft == rgt) {
return true;
@@ -91,6 +96,7 @@
* and should be avoided).
*
* @param <E> Inner object type.
+ * @hide
*/
public static final class Mutable<E> {
public E value;
@@ -106,7 +112,9 @@
/**
* Similar to Arrays.deepHashCode, but also take care of lists.
+ * @hide
*/
+ @SystemApi
public static int deepHashCode(Object o) {
if (o == null) {
return 0;
@@ -133,6 +141,7 @@
return o.hashCode();
}
+ /** @hide */
private static void throwErrorIfUnsupportedType(Object o) {
if (o instanceof Collection<?> && !(o instanceof List<?>)) {
throw new UnsupportedOperationException(
@@ -146,6 +155,7 @@
}
}
+ /** @hide */
private static int primitiveArrayHashCode(Object o) {
Class<?> elementType = o.getClass().getComponentType();
if (elementType == boolean.class) {
@@ -185,7 +195,9 @@
* - If both interfaces are stubs, asBinder() returns the object itself. By default,
* auto-generated IFoo.Stub does not override equals(), but an implementation can
* optionally override it, and {@code interfacesEqual} will use it here.
+ * @hide
*/
+ @SystemApi
public static boolean interfacesEqual(IHwInterface lft, Object rgt) {
if (lft == rgt) {
return true;
@@ -201,6 +213,10 @@
/**
* Return PID of process if sharable to clients.
+ * @hide
*/
public static native int getPidIfSharable();
+
+ /** @hide */
+ public HidlSupport() {}
}
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index 5e2a081..ecac002 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -16,16 +16,20 @@
package android.os;
+import android.annotation.SystemApi;
+
import libcore.util.NativeAllocationRegistry;
import java.util.NoSuchElementException;
/** @hide */
+@SystemApi
public abstract class HwBinder implements IHwBinder {
private static final String TAG = "HwBinder";
private static final NativeAllocationRegistry sNativeRegistry;
+ /** @hide */
public HwBinder() {
native_setup();
@@ -34,33 +38,55 @@
mNativeContext);
}
+ /** @hide */
@Override
public final native void transact(
int code, HwParcel request, HwParcel reply, int flags)
throws RemoteException;
+ /** @hide */
public abstract void onTransact(
int code, HwParcel request, HwParcel reply, int flags)
throws RemoteException;
+ /** @hide */
public native final void registerService(String serviceName)
throws RemoteException;
+ /** @hide */
public static final IHwBinder getService(
String iface,
String serviceName)
throws RemoteException, NoSuchElementException {
return getService(iface, serviceName, false /* retry */);
}
+ /** @hide */
public static native final IHwBinder getService(
String iface,
String serviceName,
boolean retry)
throws RemoteException, NoSuchElementException;
+ /**
+ * Configures how many threads the process-wide hwbinder threadpool
+ * has to process incoming requests.
+ *
+ * @hide
+ */
+ @SystemApi
public static native final void configureRpcThreadpool(
long maxThreads, boolean callerWillJoin);
+ /**
+ * Current thread will join hwbinder threadpool and process
+ * commands in the pool. Should be called after configuring
+ * a threadpool with callerWillJoin true and then registering
+ * the provided service if this thread doesn't need to do
+ * anything else.
+ *
+ * @hide
+ */
+ @SystemApi
public static native final void joinRpcThreadpool();
// Returns address of the "freeFunction".
@@ -83,6 +109,7 @@
/**
* Notifies listeners that a system property has changed
+ * @hide
*/
public static void reportSyspropChanged() {
native_report_sysprop_change();
diff --git a/core/java/android/os/HwBlob.java b/core/java/android/os/HwBlob.java
index 5e9b9ae3..405651e 100644
--- a/core/java/android/os/HwBlob.java
+++ b/core/java/android/os/HwBlob.java
@@ -17,10 +17,17 @@
package android.os;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import libcore.util.NativeAllocationRegistry;
-/** @hide */
+/**
+ * Represents fixed sized allocation of marshalled data used. Helper methods
+ * allow for access to the unmarshalled data in a variety of ways.
+ *
+ * @hide
+ */
+@SystemApi
public class HwBlob {
private static final String TAG = "HwBlob";
@@ -34,48 +41,276 @@
mNativeContext);
}
+ /**
+ * @param offset offset to unmarshall a boolean from
+ * @return the unmarshalled boolean value
+ * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+ */
public native final boolean getBool(long offset);
+ /**
+ * @param offset offset to unmarshall a byte from
+ * @return the unmarshalled byte value
+ * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+ */
public native final byte getInt8(long offset);
+ /**
+ * @param offset offset to unmarshall a short from
+ * @return the unmarshalled short value
+ * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+ */
public native final short getInt16(long offset);
+ /**
+ * @param offset offset to unmarshall an int from
+ * @return the unmarshalled int value
+ * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+ */
public native final int getInt32(long offset);
+ /**
+ * @param offset offset to unmarshall a long from
+ * @return the unmarshalled long value
+ * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+ */
public native final long getInt64(long offset);
+ /**
+ * @param offset offset to unmarshall a float from
+ * @return the unmarshalled float value
+ * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+ */
public native final float getFloat(long offset);
+ /**
+ * @param offset offset to unmarshall a double from
+ * @return the unmarshalled double value
+ * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+ */
public native final double getDouble(long offset);
+ /**
+ * @param offset offset to unmarshall a string from
+ * @return the unmarshalled string value
+ * @throws IndexOutOfBoundsException when offset is out of this HwBlob
+ */
public native final String getString(long offset);
/**
- The copyTo... methods copy the blob's data, starting from the given
- byte offset, into the array. A total of "size" _elements_ are copied.
+ * Copy the blobs data starting from the given byte offset into the range, copying
+ * a total of size elements.
+ *
+ * @param offset starting location in blob
+ * @param array destination array
+ * @param size total number of elements to copy
+ * @throws IllegalArgumentException array.length < size
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jboolean)] out of the blob.
*/
public native final void copyToBoolArray(long offset, boolean[] array, int size);
+ /**
+ * Copy the blobs data starting from the given byte offset into the range, copying
+ * a total of size elements.
+ *
+ * @param offset starting location in blob
+ * @param array destination array
+ * @param size total number of elements to copy
+ * @throws IllegalArgumentException array.length < size
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jbyte)] out of the blob.
+ */
public native final void copyToInt8Array(long offset, byte[] array, int size);
+ /**
+ * Copy the blobs data starting from the given byte offset into the range, copying
+ * a total of size elements.
+ *
+ * @param offset starting location in blob
+ * @param array destination array
+ * @param size total number of elements to copy
+ * @throws IllegalArgumentException array.length < size
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jshort)] out of the blob.
+ */
public native final void copyToInt16Array(long offset, short[] array, int size);
+ /**
+ * Copy the blobs data starting from the given byte offset into the range, copying
+ * a total of size elements.
+ *
+ * @param offset starting location in blob
+ * @param array destination array
+ * @param size total number of elements to copy
+ * @throws IllegalArgumentException array.length < size
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jint)] out of the blob.
+ */
public native final void copyToInt32Array(long offset, int[] array, int size);
+ /**
+ * Copy the blobs data starting from the given byte offset into the range, copying
+ * a total of size elements.
+ *
+ * @param offset starting location in blob
+ * @param array destination array
+ * @param size total number of elements to copy
+ * @throws IllegalArgumentException array.length < size
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jlong)] out of the blob.
+ */
public native final void copyToInt64Array(long offset, long[] array, int size);
+ /**
+ * Copy the blobs data starting from the given byte offset into the range, copying
+ * a total of size elements.
+ *
+ * @param offset starting location in blob
+ * @param array destination array
+ * @param size total number of elements to copy
+ * @throws IllegalArgumentException array.length < size
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jfloat)] out of the blob.
+ */
public native final void copyToFloatArray(long offset, float[] array, int size);
+ /**
+ * Copy the blobs data starting from the given byte offset into the range, copying
+ * a total of size elements.
+ *
+ * @param offset starting location in blob
+ * @param array destination array
+ * @param size total number of elements to copy
+ * @throws IllegalArgumentException array.length < size
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jdouble)] out of the blob.
+ */
public native final void copyToDoubleArray(long offset, double[] array, int size);
+ /**
+ * Writes a boolean value at an offset.
+ *
+ * @param offset location to write value
+ * @param x value to write
+ * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jboolean)] is out of range
+ */
public native final void putBool(long offset, boolean x);
+ /**
+ * Writes a byte value at an offset.
+ *
+ * @param offset location to write value
+ * @param x value to write
+ * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jbyte)] is out of range
+ */
public native final void putInt8(long offset, byte x);
+ /**
+ * Writes a short value at an offset.
+ *
+ * @param offset location to write value
+ * @param x value to write
+ * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jshort)] is out of range
+ */
public native final void putInt16(long offset, short x);
+ /**
+ * Writes a int value at an offset.
+ *
+ * @param offset location to write value
+ * @param x value to write
+ * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jint)] is out of range
+ */
public native final void putInt32(long offset, int x);
+ /**
+ * Writes a long value at an offset.
+ *
+ * @param offset location to write value
+ * @param x value to write
+ * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jlong)] is out of range
+ */
public native final void putInt64(long offset, long x);
+ /**
+ * Writes a float value at an offset.
+ *
+ * @param offset location to write value
+ * @param x value to write
+ * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jfloat)] is out of range
+ */
public native final void putFloat(long offset, float x);
+ /**
+ * Writes a double value at an offset.
+ *
+ * @param offset location to write value
+ * @param x value to write
+ * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jdouble)] is out of range
+ */
public native final void putDouble(long offset, double x);
+ /**
+ * Writes a string value at an offset.
+ *
+ * @param offset location to write value
+ * @param x value to write
+ * @throws IndexOutOfBoundsException when [offset, offset + sizeof(jstring)] is out of range
+ */
public native final void putString(long offset, String x);
+ /**
+ * Put a boolean array contiguously at an offset in the blob.
+ *
+ * @param offset location to write values
+ * @param x array to write
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jboolean)] out of the blob.
+ */
public native final void putBoolArray(long offset, boolean[] x);
+ /**
+ * Put a byte array contiguously at an offset in the blob.
+ *
+ * @param offset location to write values
+ * @param x array to write
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jbyte)] out of the blob.
+ */
public native final void putInt8Array(long offset, byte[] x);
+ /**
+ * Put a short array contiguously at an offset in the blob.
+ *
+ * @param offset location to write values
+ * @param x array to write
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jshort)] out of the blob.
+ */
public native final void putInt16Array(long offset, short[] x);
+ /**
+ * Put a int array contiguously at an offset in the blob.
+ *
+ * @param offset location to write values
+ * @param x array to write
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jint)] out of the blob.
+ */
public native final void putInt32Array(long offset, int[] x);
+ /**
+ * Put a long array contiguously at an offset in the blob.
+ *
+ * @param offset location to write values
+ * @param x array to write
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jlong)] out of the blob.
+ */
public native final void putInt64Array(long offset, long[] x);
+ /**
+ * Put a float array contiguously at an offset in the blob.
+ *
+ * @param offset location to write values
+ * @param x array to write
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jfloat)] out of the blob.
+ */
public native final void putFloatArray(long offset, float[] x);
+ /**
+ * Put a double array contiguously at an offset in the blob.
+ *
+ * @param offset location to write values
+ * @param x array to write
+ * @throws IndexOutOfBoundsException [offset, offset + size * sizeof(jdouble)] out of the blob.
+ */
public native final void putDoubleArray(long offset, double[] x);
+ /**
+ * Write another HwBlob into this blob at the specified location.
+ *
+ * @param offset location to write value
+ * @param blob data to write
+ * @throws IndexOutOfBoundsException if [offset, offset + blob's size] outside of the range of
+ * this blob.
+ */
public native final void putBlob(long offset, HwBlob blob);
+ /**
+ * @return current handle of HwBlob for reference in a parcelled binder transaction
+ */
public native final long handle();
+ /**
+ * Convert a primitive to a wrapped array for boolean.
+ *
+ * @param array from array
+ * @return transformed array
+ */
public static Boolean[] wrapArray(@NonNull boolean[] array) {
final int n = array.length;
Boolean[] wrappedArray = new Boolean[n];
@@ -85,6 +320,12 @@
return wrappedArray;
}
+ /**
+ * Convert a primitive to a wrapped array for long.
+ *
+ * @param array from array
+ * @return transformed array
+ */
public static Long[] wrapArray(@NonNull long[] array) {
final int n = array.length;
Long[] wrappedArray = new Long[n];
@@ -94,6 +335,12 @@
return wrappedArray;
}
+ /**
+ * Convert a primitive to a wrapped array for byte.
+ *
+ * @param array from array
+ * @return transformed array
+ */
public static Byte[] wrapArray(@NonNull byte[] array) {
final int n = array.length;
Byte[] wrappedArray = new Byte[n];
@@ -103,6 +350,12 @@
return wrappedArray;
}
+ /**
+ * Convert a primitive to a wrapped array for short.
+ *
+ * @param array from array
+ * @return transformed array
+ */
public static Short[] wrapArray(@NonNull short[] array) {
final int n = array.length;
Short[] wrappedArray = new Short[n];
@@ -112,6 +365,12 @@
return wrappedArray;
}
+ /**
+ * Convert a primitive to a wrapped array for int.
+ *
+ * @param array from array
+ * @return transformed array
+ */
public static Integer[] wrapArray(@NonNull int[] array) {
final int n = array.length;
Integer[] wrappedArray = new Integer[n];
@@ -121,6 +380,12 @@
return wrappedArray;
}
+ /**
+ * Convert a primitive to a wrapped array for float.
+ *
+ * @param array from array
+ * @return transformed array
+ */
public static Float[] wrapArray(@NonNull float[] array) {
final int n = array.length;
Float[] wrappedArray = new Float[n];
@@ -130,6 +395,12 @@
return wrappedArray;
}
+ /**
+ * Convert a primitive to a wrapped array for double.
+ *
+ * @param array from array
+ * @return transformed array
+ */
public static Double[] wrapArray(@NonNull double[] array) {
final int n = array.length;
Double[] wrappedArray = new Double[n];
diff --git a/core/java/android/os/HwParcel.java b/core/java/android/os/HwParcel.java
index 4ba1144..0eb62c95 100644
--- a/core/java/android/os/HwParcel.java
+++ b/core/java/android/os/HwParcel.java
@@ -16,17 +16,32 @@
package android.os;
-import java.util.ArrayList;
-import java.util.Arrays;
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
import libcore.util.NativeAllocationRegistry;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+
/** @hide */
+@SystemApi
public class HwParcel {
private static final String TAG = "HwParcel";
+ @IntDef(prefix = { "STATUS_" }, value = {
+ STATUS_SUCCESS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {}
+
+ /**
+ * Success return error for a transaction. Written to parcels
+ * using writeStatus.
+ */
public static final int STATUS_SUCCESS = 0;
- public static final int STATUS_ERROR = -1;
private static final NativeAllocationRegistry sNativeRegistry;
@@ -38,6 +53,9 @@
mNativeContext);
}
+ /**
+ * Creates an initialized and empty parcel.
+ */
public HwParcel() {
native_setup(true /* allocate */);
@@ -46,25 +64,106 @@
mNativeContext);
}
+ /**
+ * Writes an interface token into the parcel used to verify that
+ * a transaction has made it to the write type of interface.
+ *
+ * @param interfaceName fully qualified name of interface message
+ * is being sent to.
+ */
public native final void writeInterfaceToken(String interfaceName);
+ /**
+ * Writes a boolean value to the end of the parcel.
+ * @param val to write
+ */
public native final void writeBool(boolean val);
+ /**
+ * Writes a byte value to the end of the parcel.
+ * @param val to write
+ */
public native final void writeInt8(byte val);
+ /**
+ * Writes a short value to the end of the parcel.
+ * @param val to write
+ */
public native final void writeInt16(short val);
+ /**
+ * Writes a int value to the end of the parcel.
+ * @param val to write
+ */
public native final void writeInt32(int val);
+ /**
+ * Writes a long value to the end of the parcel.
+ * @param val to write
+ */
public native final void writeInt64(long val);
+ /**
+ * Writes a float value to the end of the parcel.
+ * @param val to write
+ */
public native final void writeFloat(float val);
+ /**
+ * Writes a double value to the end of the parcel.
+ * @param val to write
+ */
public native final void writeDouble(double val);
+ /**
+ * Writes a String value to the end of the parcel.
+ *
+ * Note, this will be converted to UTF-8 when it is written.
+ *
+ * @param val to write
+ */
public native final void writeString(String val);
+ /**
+ * Writes an array of boolean values to the end of the parcel.
+ * @param val to write
+ */
private native final void writeBoolVector(boolean[] val);
+ /**
+ * Writes an array of byte values to the end of the parcel.
+ * @param val to write
+ */
private native final void writeInt8Vector(byte[] val);
+ /**
+ * Writes an array of short values to the end of the parcel.
+ * @param val to write
+ */
private native final void writeInt16Vector(short[] val);
+ /**
+ * Writes an array of int values to the end of the parcel.
+ * @param val to write
+ */
private native final void writeInt32Vector(int[] val);
+ /**
+ * Writes an array of long values to the end of the parcel.
+ * @param val to write
+ */
private native final void writeInt64Vector(long[] val);
+ /**
+ * Writes an array of float values to the end of the parcel.
+ * @param val to write
+ */
private native final void writeFloatVector(float[] val);
+ /**
+ * Writes an array of double values to the end of the parcel.
+ * @param val to write
+ */
private native final void writeDoubleVector(double[] val);
+ /**
+ * Writes an array of String values to the end of the parcel.
+ *
+ * Note, these will be converted to UTF-8 as they are written.
+ *
+ * @param val to write
+ */
private native final void writeStringVector(String[] val);
+ /**
+ * Helper method to write a list of Booleans to val.
+ * @param val list to write
+ */
public final void writeBoolVector(ArrayList<Boolean> val) {
final int n = val.size();
boolean[] array = new boolean[n];
@@ -75,6 +174,10 @@
writeBoolVector(array);
}
+ /**
+ * Helper method to write a list of Booleans to the end of the parcel.
+ * @param val list to write
+ */
public final void writeInt8Vector(ArrayList<Byte> val) {
final int n = val.size();
byte[] array = new byte[n];
@@ -85,6 +188,10 @@
writeInt8Vector(array);
}
+ /**
+ * Helper method to write a list of Shorts to the end of the parcel.
+ * @param val list to write
+ */
public final void writeInt16Vector(ArrayList<Short> val) {
final int n = val.size();
short[] array = new short[n];
@@ -95,6 +202,10 @@
writeInt16Vector(array);
}
+ /**
+ * Helper method to write a list of Integers to the end of the parcel.
+ * @param val list to write
+ */
public final void writeInt32Vector(ArrayList<Integer> val) {
final int n = val.size();
int[] array = new int[n];
@@ -105,6 +216,10 @@
writeInt32Vector(array);
}
+ /**
+ * Helper method to write a list of Longs to the end of the parcel.
+ * @param val list to write
+ */
public final void writeInt64Vector(ArrayList<Long> val) {
final int n = val.size();
long[] array = new long[n];
@@ -115,6 +230,10 @@
writeInt64Vector(array);
}
+ /**
+ * Helper method to write a list of Floats to the end of the parcel.
+ * @param val list to write
+ */
public final void writeFloatVector(ArrayList<Float> val) {
final int n = val.size();
float[] array = new float[n];
@@ -125,6 +244,10 @@
writeFloatVector(array);
}
+ /**
+ * Helper method to write a list of Doubles to the end of the parcel.
+ * @param val list to write
+ */
public final void writeDoubleVector(ArrayList<Double> val) {
final int n = val.size();
double[] array = new double[n];
@@ -135,93 +258,272 @@
writeDoubleVector(array);
}
+ /**
+ * Helper method to write a list of Strings to the end of the parcel.
+ * @param val list to write
+ */
public final void writeStringVector(ArrayList<String> val) {
writeStringVector(val.toArray(new String[val.size()]));
}
+ /**
+ * Write a hwbinder object to the end of the parcel.
+ * @param binder value to write
+ */
public native final void writeStrongBinder(IHwBinder binder);
+ /**
+ * Checks to make sure that the interface name matches the name written by the parcel
+ * sender by writeInterfaceToken
+ *
+ * @throws SecurityException interface doesn't match
+ */
public native final void enforceInterface(String interfaceName);
+
+ /**
+ * Reads a boolean value from the current location in the parcel.
+ * @return value parsed from the parcel
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public native final boolean readBool();
+ /**
+ * Reads a byte value from the current location in the parcel.
+ * @return value parsed from the parcel
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public native final byte readInt8();
+ /**
+ * Reads a short value from the current location in the parcel.
+ * @return value parsed from the parcel
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public native final short readInt16();
+ /**
+ * Reads a int value from the current location in the parcel.
+ * @return value parsed from the parcel
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public native final int readInt32();
+ /**
+ * Reads a long value from the current location in the parcel.
+ * @return value parsed from the parcel
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public native final long readInt64();
+ /**
+ * Reads a float value from the current location in the parcel.
+ * @return value parsed from the parcel
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public native final float readFloat();
+ /**
+ * Reads a double value from the current location in the parcel.
+ * @return value parsed from the parcel
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public native final double readDouble();
+ /**
+ * Reads a String value from the current location in the parcel.
+ * @return value parsed from the parcel
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public native final String readString();
+ /**
+ * Reads an array of boolean values from the parcel.
+ * @return array of parsed values
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
private native final boolean[] readBoolVectorAsArray();
+ /**
+ * Reads an array of byte values from the parcel.
+ * @return array of parsed values
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
private native final byte[] readInt8VectorAsArray();
+ /**
+ * Reads an array of short values from the parcel.
+ * @return array of parsed values
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
private native final short[] readInt16VectorAsArray();
+ /**
+ * Reads an array of int values from the parcel.
+ * @return array of parsed values
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
private native final int[] readInt32VectorAsArray();
+ /**
+ * Reads an array of long values from the parcel.
+ * @return array of parsed values
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
private native final long[] readInt64VectorAsArray();
+ /**
+ * Reads an array of float values from the parcel.
+ * @return array of parsed values
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
private native final float[] readFloatVectorAsArray();
+ /**
+ * Reads an array of double values from the parcel.
+ * @return array of parsed values
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
private native final double[] readDoubleVectorAsArray();
+ /**
+ * Reads an array of String values from the parcel.
+ * @return array of parsed values
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
private native final String[] readStringVectorAsArray();
+ /**
+ * Convenience method to read a Boolean vector as an ArrayList.
+ * @return array of parsed values.
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public final ArrayList<Boolean> readBoolVector() {
Boolean[] array = HwBlob.wrapArray(readBoolVectorAsArray());
return new ArrayList<Boolean>(Arrays.asList(array));
}
+ /**
+ * Convenience method to read a Byte vector as an ArrayList.
+ * @return array of parsed values.
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public final ArrayList<Byte> readInt8Vector() {
Byte[] array = HwBlob.wrapArray(readInt8VectorAsArray());
return new ArrayList<Byte>(Arrays.asList(array));
}
+ /**
+ * Convenience method to read a Short vector as an ArrayList.
+ * @return array of parsed values.
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public final ArrayList<Short> readInt16Vector() {
Short[] array = HwBlob.wrapArray(readInt16VectorAsArray());
return new ArrayList<Short>(Arrays.asList(array));
}
+ /**
+ * Convenience method to read a Integer vector as an ArrayList.
+ * @return array of parsed values.
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public final ArrayList<Integer> readInt32Vector() {
Integer[] array = HwBlob.wrapArray(readInt32VectorAsArray());
return new ArrayList<Integer>(Arrays.asList(array));
}
+ /**
+ * Convenience method to read a Long vector as an ArrayList.
+ * @return array of parsed values.
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public final ArrayList<Long> readInt64Vector() {
Long[] array = HwBlob.wrapArray(readInt64VectorAsArray());
return new ArrayList<Long>(Arrays.asList(array));
}
+ /**
+ * Convenience method to read a Float vector as an ArrayList.
+ * @return array of parsed values.
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public final ArrayList<Float> readFloatVector() {
Float[] array = HwBlob.wrapArray(readFloatVectorAsArray());
return new ArrayList<Float>(Arrays.asList(array));
}
+ /**
+ * Convenience method to read a Double vector as an ArrayList.
+ * @return array of parsed values.
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public final ArrayList<Double> readDoubleVector() {
Double[] array = HwBlob.wrapArray(readDoubleVectorAsArray());
return new ArrayList<Double>(Arrays.asList(array));
}
+ /**
+ * Convenience method to read a String vector as an ArrayList.
+ * @return array of parsed values.
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public final ArrayList<String> readStringVector() {
return new ArrayList<String>(Arrays.asList(readStringVectorAsArray()));
}
+ /**
+ * Reads a strong binder value from the parcel.
+ * @return binder object read from parcel or null if no binder can be read
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public native final IHwBinder readStrongBinder();
- // Handle is stored as part of the blob.
+ /**
+ * Read opaque segment of data as a blob.
+ * @return blob of size expectedSize
+ * @throws IllegalArgumentException if the parcel has no more data
+ */
public native final HwBlob readBuffer(long expectedSize);
+ /**
+ * Read a buffer written using scatter gather.
+ *
+ * @param expectedSize size that buffer should be
+ * @param parentHandle handle from which to read the embedded buffer
+ * @param offset offset into parent
+ * @param nullable whether or not to allow for a null return
+ * @return blob of data with size expectedSize
+ * @throws NoSuchElementException if an embedded buffer is not available to read
+ * @throws IllegalArgumentException if expectedSize < 0
+ * @throws NullPointerException if the transaction specified the blob to be null
+ * but nullable is false
+ */
public native final HwBlob readEmbeddedBuffer(
long expectedSize, long parentHandle, long offset,
boolean nullable);
+ /**
+ * Write a buffer into the transaction.
+ * @param blob blob to write into the parcel.
+ */
public native final void writeBuffer(HwBlob blob);
-
+ /**
+ * Write a status value into the blob.
+ * @param status value to write
+ */
public native final void writeStatus(int status);
+ /**
+ * @throws IllegalArgumentException if a success vaue cannot be read
+ * @throws RemoteException if success value indicates a transaction error
+ */
public native final void verifySuccess();
+ /**
+ * Should be called to reduce memory pressure when this object no longer needs
+ * to be written to.
+ */
public native final void releaseTemporaryStorage();
+ /**
+ * Should be called when object is no longer needed to reduce possible memory
+ * pressure if the Java GC does not get to this object in time.
+ */
public native final void release();
+ /**
+ * Sends the parcel to the specified destination.
+ */
public native final void send();
// Returns address of the "freeFunction".
diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java
index 619f4dc..ce9f6c1 100644
--- a/core/java/android/os/IHwBinder.java
+++ b/core/java/android/os/IHwBinder.java
@@ -16,26 +16,47 @@
package android.os;
+import android.annotation.SystemApi;
+
/** @hide */
+@SystemApi
public interface IHwBinder {
// These MUST match their corresponding libhwbinder/IBinder.h definition !!!
+ /** @hide */
public static final int FIRST_CALL_TRANSACTION = 1;
+ /** @hide */
public static final int FLAG_ONEWAY = 1;
+ /** @hide */
public void transact(
int code, HwParcel request, HwParcel reply, int flags)
throws RemoteException;
+ /** @hide */
public IHwInterface queryLocalInterface(String descriptor);
/**
* Interface for receiving a callback when the process hosting a service
* has gone away.
*/
+ @SystemApi
public interface DeathRecipient {
+ /**
+ * Callback for a registered process dying.
+ */
+ @SystemApi
public void serviceDied(long cookie);
}
+ /**
+ * Notifies the death recipient with the cookie when the process containing
+ * this binder dies.
+ */
+ @SystemApi
public boolean linkToDeath(DeathRecipient recipient, long cookie);
+ /**
+ * Unregisters the death recipient from this binder.
+ */
+ @SystemApi
public boolean unlinkToDeath(DeathRecipient recipient);
}
diff --git a/core/java/android/os/IHwInterface.java b/core/java/android/os/IHwInterface.java
index 7c5ac6f..a2f59a9 100644
--- a/core/java/android/os/IHwInterface.java
+++ b/core/java/android/os/IHwInterface.java
@@ -16,7 +16,13 @@
package android.os;
+import android.annotation.SystemApi;
/** @hide */
+@SystemApi
public interface IHwInterface {
+ /**
+ * Returns the binder object that corresponds to an interface.
+ */
+ @SystemApi
public IHwBinder asBinder();
}
diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl
index 75f7c1f..1681f11 100644
--- a/core/java/android/os/IPowerManager.aidl
+++ b/core/java/android/os/IPowerManager.aidl
@@ -63,11 +63,6 @@
// --- deprecated ---
boolean isScreenBrightnessBoosted();
- // temporarily overrides the screen brightness settings to allow the user to
- // see the effect of a settings change without applying it immediately
- void setTemporaryScreenBrightnessSettingOverride(int brightness);
- void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float adj);
-
// sets the attention light (used by phone app only)
void setAttentionLight(boolean on, int color);
}
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index 1d2a408..8a27700 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -16,6 +16,7 @@
package android.os;
+import android.os.StatsDimensionsValue;
import android.os.StatsLogEventWrapper;
/**
@@ -55,8 +56,17 @@
StatsLogEventWrapper[] pullData(int pullCode);
/** Send a broadcast to the specified pkg and class that it should getData now. */
+ // TODO: Rename this and use a pending intent instead.
oneway void sendBroadcast(String pkg, String cls);
+ /**
+ * Requests StatsCompanionService to send a broadcast using the given intentSender
+ * (which should cast to an IIntentSender), along with the other information specified.
+ */
+ oneway void sendSubscriberBroadcast(in IBinder intentSender, long configUid, long configId,
+ long subscriptionId, long subscriptionRuleId,
+ in StatsDimensionsValue dimensionsValue);
+
/** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */
oneway void triggerUidSnapshot();
}
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 29812e8..679b49d 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -81,7 +81,7 @@
/**
* Sets a configuration with the specified config key and subscribes to updates for this
* configuration key. Broadcasts will be sent if this configuration needs to be collected.
- * The configuration must be a wire-encoded StatsDConfig. The caller specifies the name of the
+ * The configuration must be a wire-encoded StatsdConfig. The caller specifies the name of the
* package and class that should receive these broadcasts.
*
* Returns if this configuration was correctly registered.
@@ -95,4 +95,33 @@
* Returns if this configuration key was removed.
*/
boolean removeConfiguration(in long configKey);
+
+ /**
+ * Set the IIntentSender (i.e. PendingIntent) to be used when broadcasting subscriber
+ * information to the given subscriberId within the given config.
+ *
+ * Suppose that the calling uid has added a config with key configKey, and that in this config
+ * it is specified that when a particular anomaly is detected, a broadcast should be sent to
+ * a BroadcastSubscriber with id subscriberId. This function links the given intentSender with
+ * that subscriberId (for that config), so that this intentSender is used to send the broadcast
+ * when the anomaly is detected.
+ *
+ * This function can only be called by the owner (uid) of the config. It must be called each
+ * time statsd starts. Later calls overwrite previous calls; only one intentSender is stored.
+ *
+ * intentSender must be convertible into an IntentSender using IntentSender(IBinder)
+ * and cannot be null.
+ *
+ * Returns true if successful.
+ */
+ boolean setBroadcastSubscriber(long configKey, long subscriberId, in IBinder intentSender);
+
+ /**
+ * Undoes setBroadcastSubscriber() for the (configKey, subscriberId) pair.
+ * Any broadcasts associated with subscriberId will henceforth not be sent.
+ * No-op if this (configKey, subsriberId) pair was not associated with an IntentSender.
+ *
+ * Returns true if successful.
+ */
+ boolean unsetBroadcastSubscriber(long configKey, long subscriberId);
}
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 61172e1..3d17ffb 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1001,24 +1001,6 @@
return false;
}
- /**
- * Sets the brightness of the backlights (screen, keyboard, button).
- * <p>
- * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
- * </p>
- *
- * @param brightness The brightness value from 0 to 255.
- *
- * @hide Requires signature permission.
- */
- public void setBacklightBrightness(int brightness) {
- try {
- mService.setTemporaryScreenBrightnessSettingOverride(brightness);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
/**
* Returns true if the specified wake lock level is supported.
*
diff --git a/core/java/android/os/StatsDimensionsValue.aidl b/core/java/android/os/StatsDimensionsValue.aidl
new file mode 100644
index 0000000..81a14a4
--- /dev/null
+++ b/core/java/android/os/StatsDimensionsValue.aidl
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2018, 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.os;
+
+/** @hide */
+parcelable StatsDimensionsValue cpp_header "android/os/StatsDimensionsValue.h";
\ No newline at end of file
diff --git a/core/java/android/os/StatsDimensionsValue.java b/core/java/android/os/StatsDimensionsValue.java
new file mode 100644
index 0000000..257cc52
--- /dev/null
+++ b/core/java/android/os/StatsDimensionsValue.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright 2018 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.os;
+
+import android.annotation.SystemApi;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Container for statsd dimension value information, corresponding to a
+ * stats_log.proto's DimensionValue.
+ *
+ * This consists of a field (an int representing a statsd atom field)
+ * and a value (which may be one of a number of types).
+ *
+ * <p>
+ * Only a single value is held, and it is necessarily one of the following types:
+ * {@link String}, int, long, boolean, float,
+ * or tuple (i.e. {@link List} of {@code StatsDimensionsValue}).
+ *
+ * The type of value held can be retrieved using {@link #getValueType()}, which returns one of the
+ * following ints, depending on the type of value:
+ * <ul>
+ * <li>{@link #STRING_VALUE_TYPE}</li>
+ * <li>{@link #INT_VALUE_TYPE}</li>
+ * <li>{@link #LONG_VALUE_TYPE}</li>
+ * <li>{@link #BOOLEAN_VALUE_TYPE}</li>
+ * <li>{@link #FLOAT_VALUE_TYPE}</li>
+ * <li>{@link #TUPLE_VALUE_TYPE}</li>
+ * </ul>
+ * Alternatively, this can be determined using {@link #isValueType(int)} with one of these constants
+ * as a parameter.
+ * The value itself can be retrieved using the correct get...Value() function for its type.
+ *
+ * <p>
+ * The field is always an int, and always exists; it can be obtained using {@link #getField()}.
+ *
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsDimensionsValue implements Parcelable {
+ private static final String TAG = "StatsDimensionsValue";
+
+ // Values of the value type correspond to stats_log.proto's DimensionValue fields.
+ // Keep constants in sync with services/include/android/os/StatsDimensionsValue.h.
+ /** Indicates that this holds a String. */
+ public static final int STRING_VALUE_TYPE = 2;
+ /** Indicates that this holds an int. */
+ public static final int INT_VALUE_TYPE = 3;
+ /** Indicates that this holds a long. */
+ public static final int LONG_VALUE_TYPE = 4;
+ /** Indicates that this holds a boolean. */
+ public static final int BOOLEAN_VALUE_TYPE = 5;
+ /** Indicates that this holds a float. */
+ public static final int FLOAT_VALUE_TYPE = 6;
+ /** Indicates that this holds a List of StatsDimensionsValues. */
+ public static final int TUPLE_VALUE_TYPE = 7;
+
+ /** Value of a stats_log.proto DimensionsValue.field. */
+ private final int mField;
+
+ /** Type of stats_log.proto DimensionsValue.value, according to the VALUE_TYPEs above. */
+ private final int mValueType;
+
+ /**
+ * Value of a stats_log.proto DimensionsValue.value.
+ * String, Integer, Long, Boolean, Float, or StatsDimensionsValue[].
+ */
+ private final Object mValue; // immutable or array of immutables
+
+ /**
+ * Creates a {@code StatsDimensionValue} from a parcel.
+ *
+ * @hide
+ */
+ public StatsDimensionsValue(Parcel in) {
+ mField = in.readInt();
+ mValueType = in.readInt();
+ mValue = readValueFromParcel(mValueType, in);
+ }
+
+ /**
+ * Return the field, i.e. the tag of a statsd atom.
+ *
+ * @return the field
+ */
+ public int getField() {
+ return mField;
+ }
+
+ /**
+ * Retrieve the String held, if any.
+ *
+ * @return the {@link String} held if {@link #getValueType()} == {@link #STRING_VALUE_TYPE},
+ * null otherwise
+ */
+ public String getStringValue() {
+ try {
+ if (mValueType == STRING_VALUE_TYPE) return (String) mValue;
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Failed to successfully get value", e);
+ }
+ return null;
+ }
+
+ /**
+ * Retrieve the int held, if any.
+ *
+ * @return the int held if {@link #getValueType()} == {@link #INT_VALUE_TYPE}, 0 otherwise
+ */
+ public int getIntValue() {
+ try {
+ if (mValueType == INT_VALUE_TYPE) return (Integer) mValue;
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Failed to successfully get value", e);
+ }
+ return 0;
+ }
+
+ /**
+ * Retrieve the long held, if any.
+ *
+ * @return the long held if {@link #getValueType()} == {@link #LONG_VALUE_TYPE}, 0 otherwise
+ */
+ public long getLongValue() {
+ try {
+ if (mValueType == LONG_VALUE_TYPE) return (Long) mValue;
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Failed to successfully get value", e);
+ }
+ return 0;
+ }
+
+ /**
+ * Retrieve the boolean held, if any.
+ *
+ * @return the boolean held if {@link #getValueType()} == {@link #BOOLEAN_VALUE_TYPE},
+ * false otherwise
+ */
+ public boolean getBooleanValue() {
+ try {
+ if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue;
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Failed to successfully get value", e);
+ }
+ return false;
+ }
+
+ /**
+ * Retrieve the float held, if any.
+ *
+ * @return the float held if {@link #getValueType()} == {@link #FLOAT_VALUE_TYPE}, 0 otherwise
+ */
+ public float getFloatValue() {
+ try {
+ if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue;
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Failed to successfully get value", e);
+ }
+ return 0;
+ }
+
+ /**
+ * Retrieve the tuple, in the form of a {@link List} of {@link StatsDimensionsValue}, held,
+ * if any.
+ *
+ * @return the {@link List} of {@link StatsDimensionsValue} held
+ * if {@link #getValueType()} == {@link #TUPLE_VALUE_TYPE},
+ * null otherwise
+ */
+ public List<StatsDimensionsValue> getTupleValueList() {
+ if (mValueType != TUPLE_VALUE_TYPE) {
+ return null;
+ }
+ try {
+ StatsDimensionsValue[] orig = (StatsDimensionsValue[]) mValue;
+ List<StatsDimensionsValue> copy = new ArrayList<>(orig.length);
+ // Shallow copy since StatsDimensionsValue is immutable anyway
+ for (int i = 0; i < orig.length; i++) {
+ copy.add(orig[i]);
+ }
+ return copy;
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Failed to successfully get value", e);
+ return null;
+ }
+ }
+
+ /**
+ * Returns the constant representing the type of value stored, namely one of
+ * <ul>
+ * <li>{@link #STRING_VALUE_TYPE}</li>
+ * <li>{@link #INT_VALUE_TYPE}</li>
+ * <li>{@link #LONG_VALUE_TYPE}</li>
+ * <li>{@link #BOOLEAN_VALUE_TYPE}</li>
+ * <li>{@link #FLOAT_VALUE_TYPE}</li>
+ * <li>{@link #TUPLE_VALUE_TYPE}</li>
+ * </ul>
+ *
+ * @return the constant representing the type of value stored
+ */
+ public int getValueType() {
+ return mValueType;
+ }
+
+ /**
+ * Returns whether the type of value stored is equal to the given type.
+ *
+ * @param valueType int representing the type of value stored, as used in {@link #getValueType}
+ * @return true if {@link #getValueType()} is equal to {@code valueType}.
+ */
+ public boolean isValueType(int valueType) {
+ return mValueType == valueType;
+ }
+
+ /**
+ * Returns a String representing the information in this StatsDimensionValue.
+ * No guarantees are made about the format of this String.
+ *
+ * @return String representation
+ *
+ * @hide
+ */
+ // Follows the format of statsd's dimension.h toString.
+ public String toString() {
+ try {
+ StringBuilder sb = new StringBuilder();
+ sb.append(mField);
+ sb.append(":");
+ if (mValueType == TUPLE_VALUE_TYPE) {
+ sb.append("{");
+ StatsDimensionsValue[] sbvs = (StatsDimensionsValue[]) mValue;
+ for (int i = 0; i < sbvs.length; i++) {
+ sb.append(sbvs[i].toString());
+ sb.append("|");
+ }
+ sb.append("}");
+ } else {
+ sb.append(mValue.toString());
+ }
+ return sb.toString();
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Failed to successfully get value", e);
+ }
+ return "";
+ }
+
+ /**
+ * Parcelable Creator for StatsDimensionsValue.
+ */
+ public static final Parcelable.Creator<StatsDimensionsValue> CREATOR = new
+ Parcelable.Creator<StatsDimensionsValue>() {
+ public StatsDimensionsValue createFromParcel(Parcel in) {
+ return new StatsDimensionsValue(in);
+ }
+
+ public StatsDimensionsValue[] newArray(int size) {
+ return new StatsDimensionsValue[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mField);
+ out.writeInt(mValueType);
+ writeValueToParcel(mValueType, mValue, out, flags);
+ }
+
+ /** Writes mValue to a parcel. Returns true if succeeds. */
+ private static boolean writeValueToParcel(int valueType, Object value, Parcel out, int flags) {
+ try {
+ switch (valueType) {
+ case STRING_VALUE_TYPE:
+ out.writeString((String) value);
+ return true;
+ case INT_VALUE_TYPE:
+ out.writeInt((Integer) value);
+ return true;
+ case LONG_VALUE_TYPE:
+ out.writeLong((Long) value);
+ return true;
+ case BOOLEAN_VALUE_TYPE:
+ out.writeBoolean((Boolean) value);
+ return true;
+ case FLOAT_VALUE_TYPE:
+ out.writeFloat((Float) value);
+ return true;
+ case TUPLE_VALUE_TYPE: {
+ StatsDimensionsValue[] values = (StatsDimensionsValue[]) value;
+ out.writeInt(values.length);
+ for (int i = 0; i < values.length; i++) {
+ values[i].writeToParcel(out, flags);
+ }
+ return true;
+ }
+ default:
+ Slog.w(TAG, "readValue of an impossible type " + valueType);
+ return false;
+ }
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "writeValue cast failed", e);
+ return false;
+ }
+ }
+
+ /** Reads mValue from a parcel. */
+ private static Object readValueFromParcel(int valueType, Parcel parcel) {
+ switch (valueType) {
+ case STRING_VALUE_TYPE:
+ return parcel.readString();
+ case INT_VALUE_TYPE:
+ return parcel.readInt();
+ case LONG_VALUE_TYPE:
+ return parcel.readLong();
+ case BOOLEAN_VALUE_TYPE:
+ return parcel.readBoolean();
+ case FLOAT_VALUE_TYPE:
+ return parcel.readFloat();
+ case TUPLE_VALUE_TYPE: {
+ final int sz = parcel.readInt();
+ StatsDimensionsValue[] values = new StatsDimensionsValue[sz];
+ for (int i = 0; i < sz; i++) {
+ values[i] = new StatsDimensionsValue(parcel);
+ }
+ return values;
+ }
+ default:
+ Slog.w(TAG, "readValue of an impossible type " + valueType);
+ return null;
+ }
+ }
+}
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index d0c2870..d592000 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,7 +1,10 @@
package android.os;
import android.annotation.Nullable;
+import android.content.Context;
import android.os.WorkSourceProto;
+import android.provider.Settings;
+import android.provider.Settings.Global;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
@@ -109,6 +112,17 @@
}
}
+ /**
+ * Whether system services should create {@code WorkChains} (wherever possible) in the place
+ * of flat UID lists.
+ *
+ * @hide
+ */
+ public static boolean isChainedBatteryAttributionEnabled(Context context) {
+ return Settings.Global.getInt(context.getContentResolver(),
+ Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, 0) == 1;
+ }
+
/** @hide */
public int size() {
return mNum;
@@ -479,6 +493,29 @@
return mChains;
}
+ /**
+ * DO NOT USE: Hacky API provided solely for {@code GnssLocationProvider}. See
+ * {@code setReturningDiffs} as well.
+ *
+ * @hide
+ */
+ public void transferWorkChains(WorkSource other) {
+ if (mChains != null) {
+ mChains.clear();
+ }
+
+ if (other.mChains == null || other.mChains.isEmpty()) {
+ return;
+ }
+
+ if (mChains == null) {
+ mChains = new ArrayList<>(4);
+ }
+
+ mChains.addAll(other.mChains);
+ other.mChains.clear();
+ }
+
private boolean removeUids(WorkSource other) {
int N1 = mNum;
final int[] uids1 = mUids;
@@ -866,6 +903,13 @@
return mUids[0];
}
+ /**
+ * Return the tag associated with the attribution UID. See (@link #getAttributionUid}.
+ */
+ public String getAttributionTag() {
+ return mTags[0];
+ }
+
// TODO: The following three trivial getters are purely for testing and will be removed
// once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
// diffing it etc.
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index e7fd59e..d96316a 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -738,7 +738,9 @@
private static final String PATH_DOCUMENT = "document";
private static final String PATH_CHILDREN = "children";
private static final String PATH_SEARCH = "search";
- private static final String PATH_TREE = "tree";
+ // TODO(b/72055774): make private again once ScopedAccessProvider is refactored
+ /** {@hide} */
+ public static final String PATH_TREE = "tree";
private static final String PARAM_QUERY = "query";
private static final String PARAM_MANAGE = "manage";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index daf6bd5..e957842 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5510,6 +5510,27 @@
public static final String LOCATION_MODE = "location_mode";
/**
+ * The App or module that changes the location mode.
+ * @hide
+ */
+ public static final String LOCATION_CHANGER = "location_changer";
+ /**
+ * The location changer is unknown or unable to detect.
+ * @hide
+ */
+ public static final int LOCATION_CHANGER_UNKNOWN = 0;
+ /**
+ * Location settings in system settings.
+ * @hide
+ */
+ public static final int LOCATION_CHANGER_SYSTEM_SETTINGS = 1;
+ /**
+ * The location icon in drop down notification drawer.
+ * @hide
+ */
+ public static final int LOCATION_CHANGER_QUICK_SETTINGS = 2;
+
+ /**
* Location access disabled.
*
* @deprecated To check location status, use {@link LocationManager#isLocationEnabled()}. To
@@ -7881,6 +7902,7 @@
CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
+ CLONE_TO_MANAGED_PROFILE.add(LOCATION_CHANGER);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
@@ -9155,6 +9177,22 @@
BOOLEAN_VALIDATOR;
/**
+ * Whether to notify the user of carrier networks.
+ * <p>
+ * If not connected and the scan results have a carrier network, we will
+ * put this notification up. If we attempt to connect to a network or
+ * the carrier network(s) disappear, we remove the notification. When we
+ * show the notification, we will not show it again for
+ * {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time.
+ * @hide
+ */
+ public static final String WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON =
+ "wifi_carrier_networks_available_notification_on";
+
+ private static final Validator WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR =
+ BOOLEAN_VALIDATOR;
+
+ /**
* {@hide}
*/
public static final String WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON =
@@ -9335,13 +9373,52 @@
public static final String RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS =
"recommended_network_evaluator_cache_expiry_ms";
- /**
+ /**
* Settings to allow BLE scans to be enabled even when Bluetooth is turned off for
* connectivity.
* @hide
*/
- public static final String BLE_SCAN_ALWAYS_AVAILABLE =
- "ble_scan_always_enabled";
+ public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled";
+
+ /**
+ * The length in milliseconds of a BLE scan window in a low-power scan mode.
+ * @hide
+ */
+ public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms";
+
+ /**
+ * The length in milliseconds of a BLE scan window in a balanced scan mode.
+ * @hide
+ */
+ public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms";
+
+ /**
+ * The length in milliseconds of a BLE scan window in a low-latency scan mode.
+ * @hide
+ */
+ public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS =
+ "ble_scan_low_latency_window_ms";
+
+ /**
+ * The length in milliseconds of a BLE scan interval in a low-power scan mode.
+ * @hide
+ */
+ public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS =
+ "ble_scan_low_power_interval_ms";
+
+ /**
+ * The length in milliseconds of a BLE scan interval in a balanced scan mode.
+ * @hide
+ */
+ public static final String BLE_SCAN_BALANCED_INTERVAL_MS =
+ "ble_scan_balanced_interval_ms";
+
+ /**
+ * The length in milliseconds of a BLE scan interval in a low-latency scan mode.
+ * @hide
+ */
+ public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS =
+ "ble_scan_low_latency_interval_ms";
/**
* Used to save the Wifi_ON state prior to tethering.
@@ -11193,6 +11270,16 @@
*/
public static final String OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION =
"override_settings_provider_restore_any_version";
+ /**
+ * Flag to toggle whether system services report attribution chains when they attribute
+ * battery use via a {@code WorkSource}.
+ *
+ * Type: int (0 to disable, 1 to enable)
+ *
+ * @hide
+ */
+ public static final String CHAINED_BATTERY_ATTRIBUTION_ENABLED =
+ "chained_battery_attribution_enabled";
/**
* Settings to backup. This is here so that it's in the same place as the settings
@@ -11222,6 +11309,7 @@
NETWORK_RECOMMENDATIONS_ENABLED,
WIFI_WAKEUP_ENABLED,
WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON,
USE_OPEN_WIFI_PACKAGE,
WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
EMERGENCY_TONE,
@@ -11268,6 +11356,8 @@
VALIDATORS.put(PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_VALIDATOR);
VALIDATORS.put(PRIVATE_DNS_SPECIFIER, PRIVATE_DNS_SPECIFIER_VALIDATOR);
VALIDATORS.put(SOFT_AP_TIMEOUT_ENABLED, SOFT_AP_TIMEOUT_ENABLED_VALIDATOR);
+ VALIDATORS.put(WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+ WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR);
}
/**
diff --git a/core/java/android/security/IConfirmationPromptCallback.aidl b/core/java/android/security/IConfirmationPromptCallback.aidl
new file mode 100644
index 0000000..96a1a04
--- /dev/null
+++ b/core/java/android/security/IConfirmationPromptCallback.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2017, 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.security;
+
+/**
+ * This must be kept manually in sync with system/security/keystore until AIDL
+ * can generate both Java and C++ bindings.
+ *
+ * @hide
+ */
+interface IConfirmationPromptCallback {
+ oneway void onConfirmationPromptCompleted(in int result, in byte[] dataThatWasConfirmed);
+}
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index b5496e4..738eb68 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -81,4 +81,7 @@
in String wrappingKeyAlias, in byte[] maskingKey, in KeymasterArguments arguments,
in long rootSid, in long fingerprintSid,
out KeyCharacteristics characteristics);
+ int presentConfirmationPrompt(IBinder listener, String promptText, in byte[] extraData,
+ in String locale, in int uiOptionsAsFlags);
+ int cancelConfirmationPrompt(IBinder listener);
}
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index ce38ebb..6fa5312 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -347,7 +347,14 @@
TextLine line = TextLine.obtain();
line.set(paint, text, 0, textLength, Layout.DIR_LEFT_TO_RIGHT,
Layout.DIRS_ALL_LEFT_TO_RIGHT, false, null);
- fm.width = (int) Math.ceil(line.metrics(fm));
+ if (text instanceof MeasuredText) {
+ MeasuredText mt = (MeasuredText) text;
+ // Reaching here means there is only one paragraph.
+ MeasuredParagraph mp = mt.getMeasuredParagraph(0);
+ fm.width = (int) Math.ceil(mp.getWidth(0, mp.getTextLength()));
+ } else {
+ fm.width = (int) Math.ceil(line.metrics(fm));
+ }
TextLine.recycle(line);
return fm;
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index c7d4a4a..45fbf6f 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -176,6 +176,15 @@
}
/**
+ * Returns the length of the paragraph.
+ *
+ * This is always available.
+ */
+ public int getTextLength() {
+ return mTextLength;
+ }
+
+ /**
* Returns the characters to be measured.
*
* This is always available.
@@ -212,7 +221,7 @@
/**
* Returns the whole text width.
*
- * This is available only if the MeasureText is computed with computeForMeasurement.
+ * This is available only if the MeasuredParagraph is computed with buildForMeasurement.
* Returns 0 in other cases.
*/
public @FloatRange(from = 0.0f) float getWholeWidth() {
@@ -222,7 +231,7 @@
/**
* Returns the individual character's width.
*
- * This is available only if the MeasureText is computed with computeForMeasurement.
+ * This is available only if the MeasuredParagraph is computed with buildForMeasurement.
* Returns empty array in other cases.
*/
public @NonNull FloatArray getWidths() {
@@ -234,7 +243,7 @@
*
* If the input text is not a spanned string, this has one value that is the length of the text.
*
- * This is available only if the MeasureText is computed with computeForStaticLayout.
+ * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
* Returns empty array in other cases.
*/
public @NonNull IntArray getSpanEndCache() {
@@ -246,7 +255,7 @@
*
* This array holds the repeat of top, bottom, ascent, descent of font metrics value.
*
- * This is available only if the MeasureText is computed with computeForStaticLayout.
+ * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
* Returns empty array in other cases.
*/
public @NonNull IntArray getFontMetrics() {
@@ -256,7 +265,7 @@
/**
* Returns the native ptr of the MeasuredParagraph.
*
- * This is available only if the MeasureText is computed with computeForStaticLayout.
+ * This is available only if the MeasuredParagraph is computed with buildForStaticLayout.
* Returns 0 in other cases.
*/
public /* Maybe Zero */ long getNativePtr() {
@@ -264,6 +273,30 @@
}
/**
+ * Returns the width of the given range.
+ *
+ * This is not available if the MeasuredParagraph is computed with buildForBidi.
+ * Returns 0 if the MeasuredParagraph is computed with buildForBidi.
+ *
+ * @param start the inclusive start offset of the target region in the text
+ * @param end the exclusive end offset of the target region in the text
+ */
+ public float getWidth(int start, int end) {
+ if (mNativePtr == 0) {
+ // We have result in Java.
+ final float[] widths = mWidths.getRawArray();
+ float r = 0.0f;
+ for (int i = start; i < end; ++i) {
+ r += widths[i];
+ }
+ return r;
+ } else {
+ // We have result in native.
+ return nGetWidth(mNativePtr, start, end);
+ }
+ }
+
+ /**
* Generates new MeasuredParagraph for Bidi computation.
*
* If recycle is null, this returns new instance. If recycle is not null, this fills computed
@@ -357,6 +390,7 @@
@IntRange(from = 0) int end,
@NonNull TextDirectionHeuristic textDir,
boolean computeHyphenation,
+ boolean computeLayout,
@Nullable MeasuredParagraph recycle) {
final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
mt.resetAndAnalyzeBidi(text, start, end, textDir);
@@ -367,7 +401,7 @@
try {
mt.bindNativeObject(
nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
- computeHyphenation));
+ computeHyphenation, computeLayout));
} finally {
nFreeBuilder(nativeBuilderPtr);
}
@@ -397,7 +431,7 @@
}
}
mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer,
- computeHyphenation));
+ computeHyphenation, computeLayout));
} finally {
nFreeBuilder(nativeBuilderPtr);
}
@@ -595,7 +629,7 @@
*
* If forward=false is passed, returns the minimum index from the end instead.
*
- * This only works if the MeasuredParagraph is computed with computeForMeasurement.
+ * This only works if the MeasuredParagraph is computed with buildForMeasurement.
* Undefined behavior in other case.
*/
@IntRange(from = 0) int breakText(int limit, boolean forwards, float width) {
@@ -626,7 +660,7 @@
/**
* Returns the length of the substring.
*
- * This only works if the MeasuredParagraph is computed with computeForMeasurement.
+ * This only works if the MeasuredParagraph is computed with buildForMeasurement.
* Undefined behavior in other case.
*/
@FloatRange(from = 0.0f) float measure(int start, int limit) {
@@ -672,10 +706,16 @@
private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr,
@NonNull char[] text,
- boolean computeHyphenation);
+ boolean computeHyphenation,
+ boolean computeLayout);
private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
@CriticalNative
+ private static native float nGetWidth(/* Non Zero */ long nativePtr,
+ @IntRange(from = 0) int start,
+ @IntRange(from = 0) int end);
+
+ @CriticalNative
private static native /* Non Zero */ long nGetReleaseFunc();
}
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index b96b489..ff23395 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -155,6 +155,11 @@
* @return the measured text.
*/
public @NonNull MeasuredText build() {
+ return build(true /* build full layout result */);
+ }
+
+ /** @hide */
+ public @NonNull MeasuredText build(boolean computeLayout) {
final boolean needHyphenation = mBreakStrategy != Layout.BREAK_STRATEGY_SIMPLE
&& mHyphenationFrequency != Layout.HYPHENATION_FREQUENCY_NONE;
@@ -175,7 +180,7 @@
paragraphEnds.add(paraEnd);
measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
mPaint, mText, paraStart, paraEnd, mTextDir, needHyphenation,
- null /* no recycle */));
+ computeLayout, null /* no recycle */));
}
return new MeasuredText(mText, mStart, mEnd, mPaint, mTextDir, mBreakStrategy,
@@ -198,7 +203,8 @@
mText = text;
mStart = start;
mEnd = end;
- mPaint = paint;
+ // Copy the paint so that we can keep the reference of typeface in native layout result.
+ mPaint = new TextPaint(paint);
mMeasuredParagraphs = measuredTexts;
mParagraphBreakPoints = paragraphBreakPoints;
mTextDir = textDir;
@@ -283,6 +289,50 @@
return mHyphenationFrequency;
}
+ /**
+ * Returns true if the given TextPaint gives the same result of text layout for this text.
+ * @hide
+ */
+ public boolean canUseMeasuredResult(@NonNull TextPaint paint) {
+ return mPaint.getTextSize() == paint.getTextSize()
+ && mPaint.getTextSkewX() == paint.getTextSkewX()
+ && mPaint.getTextScaleX() == paint.getTextScaleX()
+ && mPaint.getLetterSpacing() == paint.getLetterSpacing()
+ && mPaint.getWordSpacing() == paint.getWordSpacing()
+ && mPaint.getFlags() == paint.getFlags() // Maybe not all flag affects text layout.
+ && mPaint.getTextLocales() == paint.getTextLocales() // need to be equals?
+ && mPaint.getFontVariationSettings() == paint.getFontVariationSettings()
+ && mPaint.getTypeface() == paint.getTypeface()
+ && TextUtils.equals(mPaint.getFontFeatureSettings(), paint.getFontFeatureSettings());
+ }
+
+ /** @hide */
+ public int findParaIndex(@IntRange(from = 0) int pos) {
+ // TODO: Maybe good to remove paragraph concept from MeasuredText and add substring layout
+ // support to StaticLayout.
+ for (int i = 0; i < mParagraphBreakPoints.length; ++i) {
+ if (pos < mParagraphBreakPoints[i]) {
+ return i;
+ }
+ }
+ throw new IndexOutOfBoundsException(
+ "pos must be less than " + mParagraphBreakPoints[mParagraphBreakPoints.length - 1]
+ + ", gave " + pos);
+ }
+
+ /** @hide */
+ public float getWidth(@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
+ final int paraIndex = findParaIndex(start);
+ final int paraStart = getParagraphStart(paraIndex);
+ final int paraEnd = getParagraphEnd(paraIndex);
+ if (start < paraStart || paraEnd < end) {
+ throw new RuntimeException("Cannot measured across the paragraph:"
+ + "para: (" + paraStart + ", " + paraEnd + "), "
+ + "request: (" + start + ", " + end + ")");
+ }
+ return getMeasuredParagraph(paraIndex).getWidth(start - paraStart, end - paraStart);
+ }
+
///////////////////////////////////////////////////////////////////////////////////////////////
// Spanned overrides
//
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 80d7ef5..e62f421 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -678,7 +678,7 @@
.setTextDirection(textDir)
.setBreakStrategy(b.mBreakStrategy)
.setHyphenationFrequency(b.mHyphenationFrequency)
- .build();
+ .build(false /* full layout is not necessary for line breaking */);
spanned = (source instanceof Spanned) ? (Spanned) source : null;
} else {
final CharSequence original = measured.getText();
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 86cc0141..55367dc 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -60,6 +60,7 @@
private char[] mChars;
private boolean mCharsValid;
private Spanned mSpanned;
+ private MeasuredText mMeasured;
// Additional width of whitespace for justification. This value is per whitespace, thus
// the line width will increase by mAddedWidth x (number of stretchable whitespaces).
@@ -118,6 +119,7 @@
tl.mSpanned = null;
tl.mTabs = null;
tl.mChars = null;
+ tl.mMeasured = null;
tl.mMetricAffectingSpanSpanSet.recycle();
tl.mCharacterStyleSpanSet.recycle();
@@ -168,6 +170,14 @@
hasReplacement = mReplacementSpanSpanSet.numberOfSpans > 0;
}
+ mMeasured = null;
+ if (text instanceof MeasuredText) {
+ MeasuredText mt = (MeasuredText) text;
+ if (mt.canUseMeasuredResult(paint)) {
+ mMeasured = mt;
+ }
+ }
+
mCharsValid = hasReplacement || hasTabs || directions != Layout.DIRS_ALL_LEFT_TO_RIGHT;
if (mCharsValid) {
@@ -736,8 +746,13 @@
return wp.getRunAdvance(mChars, start, end, contextStart, contextEnd, runIsRtl, offset);
} else {
final int delta = mStart;
- return wp.getRunAdvance(mText, delta + start, delta + end,
- delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
+ if (mMeasured == null) {
+ // TODO: Enable measured getRunAdvance for ReplacementSpan and RTL text.
+ return wp.getRunAdvance(mText, delta + start, delta + end,
+ delta + contextStart, delta + contextEnd, runIsRtl, delta + offset);
+ } else {
+ return mMeasured.getWidth(start + delta, end + delta);
+ }
}
}
diff --git a/core/java/android/util/MutableBoolean.java b/core/java/android/util/MutableBoolean.java
index ed837ab..44e73cc 100644
--- a/core/java/android/util/MutableBoolean.java
+++ b/core/java/android/util/MutableBoolean.java
@@ -17,7 +17,9 @@
package android.util;
/**
+ * @deprecated This class will be removed from a future version of the Android API.
*/
+@Deprecated
public final class MutableBoolean {
public boolean value;
diff --git a/core/java/android/util/MutableByte.java b/core/java/android/util/MutableByte.java
index cc6b00a..b9ec25d 100644
--- a/core/java/android/util/MutableByte.java
+++ b/core/java/android/util/MutableByte.java
@@ -17,7 +17,9 @@
package android.util;
/**
+ * @deprecated This class will be removed from a future version of the Android API.
*/
+@Deprecated
public final class MutableByte {
public byte value;
diff --git a/core/java/android/util/MutableChar.java b/core/java/android/util/MutableChar.java
index 9a2e2bc..9f7a9ae 100644
--- a/core/java/android/util/MutableChar.java
+++ b/core/java/android/util/MutableChar.java
@@ -17,7 +17,9 @@
package android.util;
/**
+ * @deprecated This class will be removed from a future version of the Android API.
*/
+@Deprecated
public final class MutableChar {
public char value;
diff --git a/core/java/android/util/MutableDouble.java b/core/java/android/util/MutableDouble.java
index bd7329a..56e539b 100644
--- a/core/java/android/util/MutableDouble.java
+++ b/core/java/android/util/MutableDouble.java
@@ -17,7 +17,9 @@
package android.util;
/**
+ * @deprecated This class will be removed from a future version of the Android API.
*/
+@Deprecated
public final class MutableDouble {
public double value;
diff --git a/core/java/android/util/MutableFloat.java b/core/java/android/util/MutableFloat.java
index e6f2d7d..6d7ad59 100644
--- a/core/java/android/util/MutableFloat.java
+++ b/core/java/android/util/MutableFloat.java
@@ -17,7 +17,9 @@
package android.util;
/**
+ * @deprecated This class will be removed from a future version of the Android API.
*/
+@Deprecated
public final class MutableFloat {
public float value;
diff --git a/core/java/android/util/MutableInt.java b/core/java/android/util/MutableInt.java
index a3d8606..bb24566 100644
--- a/core/java/android/util/MutableInt.java
+++ b/core/java/android/util/MutableInt.java
@@ -17,7 +17,9 @@
package android.util;
/**
+ * @deprecated This class will be removed from a future version of the Android API.
*/
+@Deprecated
public final class MutableInt {
public int value;
diff --git a/core/java/android/util/MutableLong.java b/core/java/android/util/MutableLong.java
index 575068e..86e70e1 100644
--- a/core/java/android/util/MutableLong.java
+++ b/core/java/android/util/MutableLong.java
@@ -17,7 +17,9 @@
package android.util;
/**
+ * @deprecated This class will be removed from a future version of the Android API.
*/
+@Deprecated
public final class MutableLong {
public long value;
diff --git a/core/java/android/util/MutableShort.java b/core/java/android/util/MutableShort.java
index 48fb232..b94ab07 100644
--- a/core/java/android/util/MutableShort.java
+++ b/core/java/android/util/MutableShort.java
@@ -17,7 +17,9 @@
package android.util;
/**
+ * @deprecated This class will be removed from a future version of the Android API.
*/
+@Deprecated
public final class MutableShort {
public short value;
diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java
index e0d085c..687aa83 100644
--- a/core/java/android/util/StatsManager.java
+++ b/core/java/android/util/StatsManager.java
@@ -17,19 +17,33 @@
import android.Manifest;
import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
import android.os.IBinder;
import android.os.IStatsManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+
+/*
+ *
+ *
+ *
+ *
+ * THIS ENTIRE FILE IS ONLY TEMPORARY TO PREVENT BREAKAGES OF DEPENDENCIES ON OLD APIS.
+ * The new StatsManager is to be found in android.app.StatsManager.
+ * TODO: Delete this file!
+ *
+ *
+ *
+ *
+ */
+
+
/**
* API for StatsD clients to send configurations and retrieve data.
*
* @hide
*/
-@SystemApi
-public final class StatsManager {
+public class StatsManager {
IStatsManager mService;
private static final String TAG = "StatsManager";
@@ -55,7 +69,7 @@
* Clients can send a configuration and simultaneously registers the name of a broadcast
* receiver that listens for when it should request data.
*
- * @param configKey An arbitrary string that allows clients to track the configuration.
+ * @param configKey An arbitrary integer that allows clients to track the configuration.
* @param config Wire-encoded StatsDConfig proto that specifies metrics (and all
* dependencies eg, conditions and matchers).
* @param pkg The package name to receive the broadcast.
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
index 5a09dab..62222b5 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
@@ -412,6 +412,20 @@
}
}
+ static byte[] generateFsverityRootHash(String apkPath)
+ throws IOException, SignatureNotFoundException, DigestException,
+ NoSuchAlgorithmException {
+ try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
+ SignatureInfo signatureInfo = findSignature(apk);
+ VerifiedSigner vSigner = verify(apk, false);
+ if (vSigner.verityRootHash == null) {
+ return null;
+ }
+ return ApkVerityBuilder.generateFsverityRootHash(
+ apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
+ }
+ }
+
private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
switch (sigAlgorithm) {
case SIGNATURE_RSA_PSS_WITH_SHA256:
diff --git a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
index 1b04eb2..ee6fc07 100644
--- a/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
+++ b/core/java/android/util/apk/ApkSignatureSchemeV3Verifier.java
@@ -523,6 +523,20 @@
}
}
+ static byte[] generateFsverityRootHash(String apkPath)
+ throws NoSuchAlgorithmException, DigestException, IOException,
+ SignatureNotFoundException {
+ try (RandomAccessFile apk = new RandomAccessFile(apkPath, "r")) {
+ SignatureInfo signatureInfo = findSignature(apk);
+ VerifiedSigner vSigner = verify(apk, false);
+ if (vSigner.verityRootHash == null) {
+ return null;
+ }
+ return ApkVerityBuilder.generateFsverityRootHash(
+ apk, ByteBuffer.wrap(vSigner.verityRootHash), signatureInfo);
+ }
+ }
+
private static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
switch (sigAlgorithm) {
case SIGNATURE_RSA_PSS_WITH_SHA256:
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index 8794372..de9f55b 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -427,6 +427,27 @@
}
/**
+ * Generates the FSVerity root hash from FSVerity header, extensions and Merkle tree root hash
+ * in Signing Block.
+ *
+ * @return FSverity root hash
+ */
+ public static byte[] generateFsverityRootHash(String apkPath)
+ throws NoSuchAlgorithmException, DigestException, IOException {
+ // first try v3
+ try {
+ return ApkSignatureSchemeV3Verifier.generateFsverityRootHash(apkPath);
+ } catch (SignatureNotFoundException e) {
+ // try older version
+ }
+ try {
+ return ApkSignatureSchemeV2Verifier.generateFsverityRootHash(apkPath);
+ } catch (SignatureNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
* Result of a successful APK verification operation.
*/
public static class Result {
diff --git a/core/java/android/util/apk/ApkVerityBuilder.java b/core/java/android/util/apk/ApkVerityBuilder.java
index 5880c6a..ba21ccb 100644
--- a/core/java/android/util/apk/ApkVerityBuilder.java
+++ b/core/java/android/util/apk/ApkVerityBuilder.java
@@ -70,7 +70,7 @@
throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
long signingBlockSize =
signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
- long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+ long dataSize = apk.length() - signingBlockSize;
int[] levelOffset = calculateVerityLevelOffset(dataSize);
ByteBuffer output = bufferFactory.create(
@@ -132,11 +132,13 @@
}
if (headerOutput != null) {
+ headerOutput.order(ByteOrder.LITTLE_ENDIAN);
generateFsverityHeader(headerOutput, apk.length(), levelOffset.length - 1,
DEFAULT_SALT);
}
if (extensionsOutput != null) {
+ extensionsOutput.order(ByteOrder.LITTLE_ENDIAN);
generateFsverityExtensions(extensionsOutput, signatureInfo.apkSigningBlockOffset,
signingBlockSize, signatureInfo.eocdOffset);
}
@@ -306,6 +308,14 @@
return rootHash;
}
+ private static void bufferPut(ByteBuffer buffer, byte value) {
+ // FIXME(b/72459251): buffer.put(value) does NOT work surprisingly. The position() after put
+ // does NOT even change. This hack workaround the problem, but the root cause remains
+ // unkonwn yet. This seems only happen when it goes through the apk install flow on my
+ // setup.
+ buffer.put(new byte[] { value });
+ }
+
private static ByteBuffer generateFsverityHeader(ByteBuffer buffer, long fileSize, int depth,
byte[] salt) {
if (salt.length != 8) {
@@ -315,22 +325,23 @@
// TODO(b/30972906): update the reference when there is a better one in public.
buffer.put("TrueBrew".getBytes()); // magic
- buffer.put((byte) 1); // major version
- buffer.put((byte) 0); // minor version
- buffer.put((byte) 12); // log2(block-size): log2(4096)
- buffer.put((byte) 7); // log2(leaves-per-node): log2(4096 / 32)
+ bufferPut(buffer, (byte) 1); // major version
+ bufferPut(buffer, (byte) 0); // minor version
+ bufferPut(buffer, (byte) 12); // log2(block-size): log2(4096)
+ bufferPut(buffer, (byte) 7); // log2(leaves-per-node): log2(4096 / 32)
- buffer.putShort((short) 1); // meta algorithm, SHA256_MODE == 1
- buffer.putShort((short) 1); // data algorithm, SHA256_MODE == 1
+ buffer.putShort((short) 1); // meta algorithm, SHA256_MODE == 1
+ buffer.putShort((short) 1); // data algorithm, SHA256_MODE == 1
- buffer.putInt(0x1); // flags, 0x1: has extension
- buffer.putInt(0); // reserved
+ buffer.putInt(0x1); // flags, 0x1: has extension
+ buffer.putInt(0); // reserved
- buffer.putLong(fileSize); // original file size
+ buffer.putLong(fileSize); // original file size
- buffer.put((byte) 0); // auth block offset, disabled here
- buffer.put(salt); // salt (8 bytes)
- // skip(buffer, 22); // reserved
+ bufferPut(buffer, (byte) 0); // auth block offset, disabled here
+ bufferPut(buffer, (byte) 2); // extension count
+ buffer.put(salt); // salt (8 bytes)
+ // skip(buffer, 22); // reserved
buffer.rewind();
return buffer;
@@ -340,11 +351,6 @@
long signingBlockSize, long eocdOffset) {
// Snapshot of the FSVerity structs (subject to change once upstreamed).
//
- // struct fsverity_header_extension {
- // u8 extension_count;
- // u8 reserved[7];
- // };
- //
// struct fsverity_extension {
// __le16 length;
// u8 type;
@@ -363,10 +369,6 @@
// u8 databytes[];
// };
- // struct fsverity_header_extension
- buffer.put((byte) 2); // extension count
- skip(buffer, 3); // reserved
-
final int kSizeOfFsverityExtensionHeader = 8;
{
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 17b6ddc..d7fd329 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -157,10 +157,8 @@
* @param flags See {@code View#startDragAndDrop}
* @param surface Surface containing drag shadow image
* @param touchSource See {@code InputDevice#getSource()}
- * @param touchX TODO (b/72072998): Fix the issue that the system server misuse the arguments as
- * initial touch point while the framework passes drag shadow size.
- * @param touchY TODO (b/72072998): Fix the issue that the system server misuse the arguments as
- * initial touch point while the framework passes drag shadow size.
+ * @param touchX X coordinate of last touch point
+ * @param touchY Y coordinate of last touch point
* @param thumbCenterX X coordinate for the position within the shadow image that should be
* underneath the touch point during the drag and drop operation.
* @param thumbCenterY Y coordinate for the position within the shadow image that should be
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 04fa637..1d7c1de 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -26,6 +26,8 @@
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
+import java.util.Objects;
+
/**
* Object used to report movement (mouse, pen, finger, trackball) events.
* Motion events may hold either absolute or relative movements and other data,
@@ -173,6 +175,8 @@
private static final long NS_PER_MS = 1000000;
private static final String LABEL_PREFIX = "AXIS_";
+ private static final boolean DEBUG_CONCISE_TOSTRING = false;
+
/**
* An invalid pointer id.
*
@@ -3236,31 +3240,42 @@
public String toString() {
StringBuilder msg = new StringBuilder();
msg.append("MotionEvent { action=").append(actionToString(getAction()));
- msg.append(", actionButton=").append(buttonStateToString(getActionButton()));
+ appendUnless("0", msg, ", actionButton=", buttonStateToString(getActionButton()));
final int pointerCount = getPointerCount();
for (int i = 0; i < pointerCount; i++) {
- msg.append(", id[").append(i).append("]=").append(getPointerId(i));
- msg.append(", x[").append(i).append("]=").append(getX(i));
- msg.append(", y[").append(i).append("]=").append(getY(i));
- msg.append(", toolType[").append(i).append("]=").append(
- toolTypeToString(getToolType(i)));
+ appendUnless(i, msg, ", id[" + i + "]=", getPointerId(i));
+ float x = getX(i);
+ float y = getY(i);
+ if (!DEBUG_CONCISE_TOSTRING || x != 0f || y != 0f) {
+ msg.append(", x[").append(i).append("]=").append(x);
+ msg.append(", y[").append(i).append("]=").append(y);
+ }
+ appendUnless(TOOL_TYPE_SYMBOLIC_NAMES.get(TOOL_TYPE_FINGER),
+ msg, ", toolType[" + i + "]=", toolTypeToString(getToolType(i)));
}
- msg.append(", buttonState=").append(MotionEvent.buttonStateToString(getButtonState()));
- msg.append(", metaState=").append(KeyEvent.metaStateToString(getMetaState()));
- msg.append(", flags=0x").append(Integer.toHexString(getFlags()));
- msg.append(", edgeFlags=0x").append(Integer.toHexString(getEdgeFlags()));
- msg.append(", pointerCount=").append(pointerCount);
- msg.append(", historySize=").append(getHistorySize());
+ appendUnless("0", msg, ", buttonState=", MotionEvent.buttonStateToString(getButtonState()));
+ appendUnless("0", msg, ", metaState=", KeyEvent.metaStateToString(getMetaState()));
+ appendUnless("0", msg, ", flags=0x", Integer.toHexString(getFlags()));
+ appendUnless("0", msg, ", edgeFlags=0x", Integer.toHexString(getEdgeFlags()));
+ appendUnless(1, msg, ", pointerCount=", pointerCount);
+ appendUnless(0, msg, ", historySize=", getHistorySize());
msg.append(", eventTime=").append(getEventTime());
- msg.append(", downTime=").append(getDownTime());
- msg.append(", deviceId=").append(getDeviceId());
- msg.append(", source=0x").append(Integer.toHexString(getSource()));
+ if (!DEBUG_CONCISE_TOSTRING) {
+ msg.append(", downTime=").append(getDownTime());
+ msg.append(", deviceId=").append(getDeviceId());
+ msg.append(", source=0x").append(Integer.toHexString(getSource()));
+ }
msg.append(" }");
return msg.toString();
}
+ private static <T> void appendUnless(T defValue, StringBuilder sb, String key, T value) {
+ if (DEBUG_CONCISE_TOSTRING && Objects.equals(defValue, value)) return;
+ sb.append(key).append(value);
+ }
+
/**
* Returns a string that represents the symbolic name of the specified unmasked action
* such as "ACTION_DOWN", "ACTION_POINTER_DOWN(3)" or an equivalent numeric constant
diff --git a/core/java/android/view/RecordingCanvas.java b/core/java/android/view/RecordingCanvas.java
index 5088cdc..fbb862b 100644
--- a/core/java/android/view/RecordingCanvas.java
+++ b/core/java/android/view/RecordingCanvas.java
@@ -34,6 +34,7 @@
import android.graphics.RectF;
import android.graphics.TemporaryBuffer;
import android.text.GraphicsOperations;
+import android.text.MeasuredText;
import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils;
@@ -473,7 +474,8 @@
}
nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
- x, y, isRtl, paint.getNativeInstance());
+ x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */,
+ 0 /* measured text offset */);
}
@Override
@@ -503,8 +505,20 @@
int len = end - start;
char[] buf = TemporaryBuffer.obtain(contextLen);
TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+ long measuredTextPtr = 0;
+ int measuredTextOffset = 0;
+ if (text instanceof MeasuredText) {
+ MeasuredText mt = (MeasuredText) text;
+ int paraIndex = mt.findParaIndex(start);
+ if (end <= mt.getParagraphEnd(paraIndex)) {
+ // Only support if the target is in the same paragraph.
+ measuredTextPtr = mt.getMeasuredParagraph(paraIndex).getNativePtr();
+ measuredTextOffset = start - mt.getParagraphStart(paraIndex);
+ }
+ }
nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
- 0, contextLen, x, y, isRtl, paint.getNativeInstance());
+ 0, contextLen, x, y, isRtl, paint.getNativeInstance(),
+ measuredTextPtr, measuredTextOffset);
TemporaryBuffer.recycle(buf);
}
}
@@ -626,7 +640,8 @@
@FastNative
private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count,
- int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint);
+ int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint,
+ long nativeMeasuredText, int measuredTextOffset);
@FastNative
private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count,
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 578679b..4a9da4a 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -25,6 +25,7 @@
import android.content.res.CompatibilityInfo.Translator;
import android.content.res.Configuration;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
@@ -114,7 +115,7 @@
final Rect mScreenRect = new Rect();
SurfaceSession mSurfaceSession;
- SurfaceControl mSurfaceControl;
+ SurfaceControlWithBackground mSurfaceControl;
// In the case of format changes we switch out the surface in-place
// we need to preserve the old one until the new one has drawn.
SurfaceControl mDeferredDestroySurfaceControl;
@@ -925,6 +926,17 @@
return mSubLayer >= 0;
}
+ /**
+ * Set an opaque background color to use with this {@link SurfaceView} when it's being resized
+ * and size of the content hasn't updated yet. This color will fill the expanded area when the
+ * view becomes larger.
+ * @param bgColor An opaque color to fill the background. Alpha component will be ignored.
+ * @hide
+ */
+ public void setResizeBackgroundColor(int bgColor) {
+ mSurfaceControl.setBackgroundColor(bgColor);
+ }
+
private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
private static final String LOG_TAG = "SurfaceHolder";
@@ -1219,6 +1231,19 @@
mBackgroundControl.deferTransactionUntil(barrier, frame);
}
+ /** Set the color to fill the background with. */
+ private void setBackgroundColor(int bgColor) {
+ final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
+ Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
+
+ SurfaceControl.openTransaction();
+ try {
+ mBackgroundControl.setColor(colorComponents);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+
void updateBackgroundVisibility() {
if (mOpaque && mVisible) {
mBackgroundControl.show();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a2ecfc4..3d6a6fe 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+
import static java.lang.Math.max;
import android.animation.AnimatorInflater;
@@ -8548,6 +8550,12 @@
info.setLongClickable(isLongClickable());
info.setContextClickable(isContextClickable());
info.setLiveRegion(getAccessibilityLiveRegion());
+ if ((mTooltipInfo != null) && (mTooltipInfo.mTooltipText != null)) {
+ info.setTooltipText(mTooltipInfo.mTooltipText);
+ info.addAction((mTooltipInfo.mTooltipPopup == null)
+ ? AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TOOLTIP
+ : AccessibilityNodeInfo.AccessibilityAction.ACTION_HIDE_TOOLTIP);
+ }
// TODO: These make sense only if we are in an AdapterView but all
// views can be selected. Maybe from accessibility perspective
@@ -8951,8 +8959,7 @@
return;
}
mAccessibilityTraversalBeforeId = beforeId;
- notifyAccessibilityStateChanged(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
}
/**
@@ -8995,8 +9002,7 @@
return;
}
mAccessibilityTraversalAfterId = afterId;
- notifyAccessibilityStateChanged(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
}
/**
@@ -9038,8 +9044,7 @@
&& mID == View.NO_ID) {
mID = generateViewId();
}
- notifyAccessibilityStateChanged(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
}
/**
@@ -10539,7 +10544,7 @@
if (pflags3 != mPrivateFlags3) {
mPrivateFlags3 = pflags3;
- notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
@@ -11253,6 +11258,8 @@
if (!isLayoutValid()) {
mPrivateFlags |= PFLAG_WANTS_FOCUS;
+ } else {
+ clearParentsWantFocus();
}
handleFocusGainInternal(direction, previouslyFocusedRect);
@@ -11367,8 +11374,7 @@
mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT)
& PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
- notifyAccessibilityStateChanged(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
@@ -11427,8 +11433,7 @@
if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) {
notifyAccessibilitySubtreeChanged();
} else {
- notifyAccessibilityStateChanged(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
}
@@ -11838,8 +11843,7 @@
|| getAccessibilitySelectionEnd() != end)
&& (start == end)) {
setAccessibilitySelection(start, end);
- notifyAccessibilityStateChanged(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
return true;
}
} break;
@@ -11856,6 +11860,21 @@
return true;
}
} break;
+ case R.id.accessibilityActionShowTooltip: {
+ if ((mTooltipInfo != null) && (mTooltipInfo.mTooltipPopup != null)) {
+ // Tooltip already showing
+ return false;
+ }
+ return showLongClickTooltip(0, 0);
+ }
+ case R.id.accessibilityActionHideTooltip: {
+ if ((mTooltipInfo == null) || (mTooltipInfo.mTooltipPopup == null)) {
+ // No tooltip showing
+ return false;
+ }
+ hideTooltip();
+ return true;
+ }
}
return false;
}
@@ -13889,12 +13908,10 @@
if (oldIncludeForAccessibility != includeForAccessibility()) {
notifyAccessibilitySubtreeChanged();
} else {
- notifyAccessibilityStateChanged(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
}
} else if ((changed & ENABLED_MASK) != 0) {
- notifyAccessibilityStateChanged(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
}
@@ -21854,8 +21871,7 @@
if (selected) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
} else {
- notifyAccessibilityStateChanged(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
}
@@ -27057,6 +27073,8 @@
final boolean fromTouch = (mPrivateFlags3 & PFLAG3_FINGER_DOWN) == PFLAG3_FINGER_DOWN;
mTooltipInfo.mTooltipPopup.show(this, x, y, fromTouch, mTooltipInfo.mTooltipText);
mAttachInfo.mTooltipHost = this;
+ // The available accessibility actions have changed
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
return true;
}
@@ -27075,6 +27093,8 @@
if (mAttachInfo != null) {
mAttachInfo.mTooltipHost = null;
}
+ // The available accessibility actions have changed
+ notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
}
private boolean showLongClickTooltip(int x, int y) {
@@ -27083,8 +27103,8 @@
return showTooltip(x, y, true);
}
- private void showHoverTooltip() {
- showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false);
+ private boolean showHoverTooltip() {
+ return showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false);
}
boolean dispatchTooltipHoverEvent(MotionEvent event) {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 5bd0782..93b3fc2 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -25,7 +25,6 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
-import android.annotation.SystemApi;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -1255,14 +1254,6 @@
}
/** @hide */
- @SystemApi
- public void setDisableWallpaperTouchEvents(boolean disable) {
- setPrivateFlags(disable
- ? WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS : 0,
- WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS);
- }
-
- /** @hide */
public abstract void alwaysReadCloseOnTouchAttr();
/** @hide */
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 3ee282e..23e7d61 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -727,6 +727,7 @@
private CharSequence mError;
private CharSequence mPaneTitle;
private CharSequence mContentDescription;
+ private CharSequence mTooltipText;
private String mViewIdResourceName;
private ArrayList<String> mExtraDataKeys;
@@ -2655,6 +2656,34 @@
}
/**
+ * Gets the tooltip text of this node.
+ *
+ * @return The tooltip text.
+ */
+ @Nullable
+ public CharSequence getTooltipText() {
+ return mTooltipText;
+ }
+
+ /**
+ * Sets the tooltip text of this node.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param tooltipText The tooltip text.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setTooltipText(@Nullable CharSequence tooltipText) {
+ enforceNotSealed();
+ mTooltipText = (tooltipText == null) ? null
+ : tooltipText.subSequence(0, tooltipText.length());
+ }
+
+ /**
* Sets the view for which the view represented by this info serves as a
* label for accessibility purposes.
*
@@ -3209,6 +3238,10 @@
nonDefaultFields |= bitAt(fieldIndex);
}
fieldIndex++;
+ if (!Objects.equals(mTooltipText, DEFAULT.mTooltipText)) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
nonDefaultFields |= bitAt(fieldIndex);
}
@@ -3329,6 +3362,8 @@
parcel.writeCharSequence(mContentDescription);
}
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
+ if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText);
+
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
@@ -3401,6 +3436,7 @@
mError = other.mError;
mContentDescription = other.mContentDescription;
mPaneTitle = other.mPaneTitle;
+ mTooltipText = other.mTooltipText;
mViewIdResourceName = other.mViewIdResourceName;
if (mActions != null) mActions.clear();
@@ -3522,6 +3558,7 @@
mContentDescription = parcel.readCharSequence();
}
if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readString();
+ if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionStart = parcel.readInt();
@@ -3684,6 +3721,10 @@
return "ACTION_SET_PROGRESS";
case R.id.accessibilityActionContextClick:
return "ACTION_CONTEXT_CLICK";
+ case R.id.accessibilityActionShowTooltip:
+ return "ACTION_SHOW_TOOLTIP";
+ case R.id.accessibilityActionHideTooltip:
+ return "ACTION_HIDE_TOOLTIP";
default:
return "ACTION_UNKNOWN";
}
@@ -3797,6 +3838,7 @@
builder.append("; error: ").append(mError);
builder.append("; maxTextLength: ").append(mMaxTextLength);
builder.append("; contentDescription: ").append(mContentDescription);
+ builder.append("; tooltipText: ").append(mTooltipText);
builder.append("; viewIdResName: ").append(mViewIdResourceName);
builder.append("; checkable: ").append(isCheckable());
@@ -4211,6 +4253,20 @@
public static final AccessibilityAction ACTION_MOVE_WINDOW =
new AccessibilityAction(R.id.accessibilityActionMoveWindow);
+ /**
+ * Action to show a tooltip. A node should expose this action only for views with tooltip
+ * text that but are not currently showing a tooltip.
+ */
+ public static final AccessibilityAction ACTION_SHOW_TOOLTIP =
+ new AccessibilityAction(R.id.accessibilityActionShowTooltip);
+
+ /**
+ * Action to hide a tooltip. A node should expose this action only for views that are
+ * currently showing a tooltip.
+ */
+ public static final AccessibilityAction ACTION_HIDE_TOOLTIP =
+ new AccessibilityAction(R.id.accessibilityActionHideTooltip);
+
private final int mActionId;
private final CharSequence mLabel;
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index e554540..eba9176 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -21,6 +21,7 @@
import android.inputmethodservice.InputMethodService;
import android.os.Bundle;
import android.os.Handler;
+import android.os.LocaleList;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -898,4 +899,37 @@
*/
boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
@Nullable Bundle opts);
+
+ /**
+ * Called by the input method to tell a hint about the locales of text to be committed.
+ *
+ * <p>This is just a hint for editor authors (and the system) to choose better options when
+ * they have to disambiguate languages, like editor authors can do for input methods with
+ * {@link EditorInfo#hintLocales}.</p>
+ *
+ * <p>The language hint provided by this callback should have higher priority than
+ * {@link InputMethodSubtype#getLanguageTag()}, which cannot be updated dynamically.</p>
+ *
+ * <p>Note that in general it is discouraged for input method to specify
+ * {@link android.text.style.LocaleSpan} when inputting text, mainly because of application
+ * compatibility concerns.</p>
+ * <ul>
+ * <li>When an existing text that already has {@link android.text.style.LocaleSpan} is being
+ * modified by both the input method and application, there is no reliable and easy way to
+ * keep track of who modified {@link android.text.style.LocaleSpan}. For instance, if the
+ * text was updated by JavaScript, it it highly likely that span information is completely
+ * removed, while some input method attempts to preserve spans if possible.</li>
+ * <li>There is no clear semantics regarding whether {@link android.text.style.LocaleSpan}
+ * means a weak (ignorable) hint or a strong hint. This becomes more problematic when
+ * multiple {@link android.text.style.LocaleSpan} instances are specified to the same
+ * text region, especially when those spans are conflicting.</li>
+ * </ul>
+ * @param languageHint list of languages sorted by the priority and/or probability
+ */
+ default void reportLanguageHint(@NonNull LocaleList languageHint) {
+ // Intentionally empty.
+ //
+ // We need to have *some* default implementation for the source compatibility.
+ // See Bug 72127682 for details.
+ }
}
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index f671e22..cbe6856 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -16,8 +16,10 @@
package android.view.inputmethod;
+import android.annotation.NonNull;
import android.os.Bundle;
import android.os.Handler;
+import android.os.LocaleList;
import android.view.KeyEvent;
/**
@@ -303,4 +305,13 @@
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
return mTarget.commitContent(inputContentInfo, flags, opts);
}
+
+ /**
+ * {@inheritDoc}
+ * @throws NullPointerException if the target is {@code null}.
+ */
+ @Override
+ public void reportLanguageHint(@NonNull LocaleList languageHint) {
+ mTarget.reportLanguageHint(languageHint);
+ }
}
diff --git a/core/java/android/view/textclassifier/SmartSelection.java b/core/java/android/view/textclassifier/SmartSelection.java
index 2c93a19..8edf97e 100644
--- a/core/java/android/view/textclassifier/SmartSelection.java
+++ b/core/java/android/view/textclassifier/SmartSelection.java
@@ -16,6 +16,7 @@
package android.view.textclassifier;
+import android.annotation.Nullable;
import android.content.res.AssetFileDescriptor;
/**
@@ -146,11 +147,24 @@
final String mCollection;
/** float range: 0 - 1 */
final float mScore;
+ @Nullable final DatetimeParseResult mDatetime;
ClassificationResult(String collection, float score) {
mCollection = collection;
mScore = score;
+ mDatetime = null;
}
+
+ ClassificationResult(String collection, float score, DatetimeParseResult datetime) {
+ mCollection = collection;
+ mScore = score;
+ mDatetime = datetime;
+ }
+ }
+
+ /** Parsed date information for the classification result. */
+ static final class DatetimeParseResult {
+ long mMsSinceEpoch;
}
/** Represents a result of Annotate call. */
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 7089677..54e93d5 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -36,6 +36,7 @@
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -592,6 +593,7 @@
public static final class Options implements Parcelable {
private @Nullable LocaleList mDefaultLocales;
+ private @Nullable Calendar mReferenceTime;
public Options() {}
@@ -606,6 +608,16 @@
}
/**
+ * @param referenceTime reference time based on which relative dates (e.g. "tomorrow" should
+ * be interpreted. This should usually be the time when the text was originally
+ * composed. If no reference time is set, now is used.
+ */
+ public Options setReferenceTime(Calendar referenceTime) {
+ mReferenceTime = referenceTime;
+ return this;
+ }
+
+ /**
* @return ordered list of locale preferences that can be used to disambiguate
* the provided text.
*/
@@ -614,6 +626,15 @@
return mDefaultLocales;
}
+ /**
+ * @return reference time based on which relative dates (e.g. "tomorrow") should be
+ * interpreted.
+ */
+ @Nullable
+ public Calendar getReferenceTime() {
+ return mReferenceTime;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -625,6 +646,10 @@
if (mDefaultLocales != null) {
mDefaultLocales.writeToParcel(dest, flags);
}
+ dest.writeInt(mReferenceTime != null ? 1 : 0);
+ if (mReferenceTime != null) {
+ dest.writeSerializable(mReferenceTime);
+ }
}
public static final Parcelable.Creator<Options> CREATOR =
@@ -644,6 +669,9 @@
if (in.readInt() > 0) {
mDefaultLocales = LocaleList.CREATOR.createFromParcel(in);
}
+ if (in.readInt() > 0) {
+ mReferenceTime = (Calendar) in.readSerializable();
+ }
}
}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index e9715c5..04ab447 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -47,12 +47,26 @@
/** @hide */
String DEFAULT_LOG_TAG = "androidtc";
+ /** The TextClassifier failed to run. */
String TYPE_UNKNOWN = "";
+ /** The classifier ran, but didn't recognize a known entity. */
String TYPE_OTHER = "other";
+ /** E-mail address (e.g. "noreply@android.com"). */
String TYPE_EMAIL = "email";
+ /** Phone number (e.g. "555-123 456"). */
String TYPE_PHONE = "phone";
+ /** Physical address. */
String TYPE_ADDRESS = "address";
+ /** Web URL. */
String TYPE_URL = "url";
+ /** Time reference that is no more specific than a date. May be absolute such as "01/01/2000" or
+ * relative like "tomorrow". **/
+ String TYPE_DATE = "date";
+ /** Time reference that includes a specific time. May be absolute such as "01/01/2000 5:30pm" or
+ * relative like "tomorrow at 5:30pm". **/
+ String TYPE_DATE_TIME = "datetime";
+ /** Flight number in IATA format. */
+ String TYPE_FLIGHT_NUMBER = "flight";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -63,6 +77,9 @@
TYPE_PHONE,
TYPE_ADDRESS,
TYPE_URL,
+ TYPE_DATE,
+ TYPE_DATE_TIME,
+ TYPE_FLIGHT_NUMBER,
})
@interface EntityType {}
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 7db0e76..f434452 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -18,7 +18,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.SearchManager;
import android.content.ComponentName;
+import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -28,6 +30,7 @@
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.provider.Browser;
+import android.provider.CalendarContract;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.text.util.Linkify;
@@ -42,6 +45,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -49,6 +53,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -73,7 +78,10 @@
TextClassifier.TYPE_ADDRESS,
TextClassifier.TYPE_EMAIL,
TextClassifier.TYPE_PHONE,
- TextClassifier.TYPE_URL));
+ TextClassifier.TYPE_URL,
+ TextClassifier.TYPE_DATE,
+ TextClassifier.TYPE_DATE_TIME,
+ TextClassifier.TYPE_FLIGHT_NUMBER));
private static final List<String> ENTITY_TYPES_BASE =
Collections.unmodifiableList(Arrays.asList(
TextClassifier.TYPE_ADDRESS,
@@ -167,9 +175,8 @@
.classifyText(string, startIndex, endIndex,
getHintFlags(string, startIndex, endIndex));
if (results.length > 0) {
- final TextClassification classificationResult =
- createClassificationResult(results, string, startIndex, endIndex);
- return classificationResult;
+ return createClassificationResult(
+ results, string, startIndex, endIndex, options.getReferenceTime());
}
}
} catch (Throwable t) {
@@ -410,18 +417,24 @@
private TextClassification createClassificationResult(
SmartSelection.ClassificationResult[] classifications,
- String text, int start, int end) {
+ String text, int start, int end, @Nullable Calendar referenceTime) {
final String classifiedText = text.substring(start, end);
final TextClassification.Builder builder = new TextClassification.Builder()
.setText(classifiedText);
final int size = classifications.length;
+ SmartSelection.ClassificationResult highestScoringResult = null;
+ float highestScore = Float.MIN_VALUE;
for (int i = 0; i < size; i++) {
builder.setEntityType(classifications[i].mCollection, classifications[i].mScore);
+ if (classifications[i].mScore > highestScore) {
+ highestScoringResult = classifications[i];
+ highestScore = classifications[i].mScore;
+ }
}
- final String type = getHighestScoringType(classifications);
- addActions(builder, IntentFactory.create(mContext, type, classifiedText));
+ addActions(builder, IntentFactory.create(
+ mContext, referenceTime, highestScoringResult, classifiedText));
return builder.setSignature(getSignature(text, start, end)).build();
}
@@ -441,11 +454,10 @@
}
if (resolveInfo != null && resolveInfo.activityInfo != null) {
final String packageName = resolveInfo.activityInfo.packageName;
- CharSequence label;
+ final String label = IntentFactory.getLabel(mContext, intent);
Drawable icon;
if ("android".equals(packageName)) {
// Requires the chooser to find an activity to handle the intent.
- label = IntentFactory.getLabel(mContext, intent);
icon = null;
} else {
// A default activity will handle the intent.
@@ -455,16 +467,11 @@
if (icon == null) {
icon = resolveInfo.loadIcon(pm);
}
- label = resolveInfo.activityInfo.loadLabel(pm);
- if (label == null) {
- label = resolveInfo.loadLabel(pm);
- }
}
- final String labelString = (label != null) ? label.toString() : null;
if (i == 0) {
- builder.setPrimaryAction(intent, labelString, icon);
+ builder.setPrimaryAction(intent, label, icon);
} else {
- builder.addSecondaryAction(intent, labelString, icon);
+ builder.addSecondaryAction(intent, label, icon);
}
}
}
@@ -483,23 +490,6 @@
return flag;
}
- private static String getHighestScoringType(SmartSelection.ClassificationResult[] types) {
- if (types.length < 1) {
- return "";
- }
-
- String type = types[0].mCollection;
- float highestScore = types[0].mScore;
- final int size = types.length;
- for (int i = 1; i < size; i++) {
- if (types[i].mScore > highestScore) {
- type = types[i].mCollection;
- highestScore = types[i].mScore;
- }
- }
- return type;
- }
-
/**
* Closes the ParcelFileDescriptor and logs any errors that occur.
*/
@@ -514,58 +504,139 @@
/**
* Creates intents based on the classification type.
*/
- private static final class IntentFactory {
+ static final class IntentFactory {
+
+ private static final long MIN_EVENT_FUTURE_MILLIS = TimeUnit.MINUTES.toMillis(5);
+ private static final long DEFAULT_EVENT_DURATION = TimeUnit.HOURS.toMillis(1);
private IntentFactory() {}
@NonNull
- public static List<Intent> create(Context context, String type, String text) {
- final List<Intent> intents = new ArrayList<>();
- type = type.trim().toLowerCase(Locale.ENGLISH);
+ public static List<Intent> create(
+ Context context,
+ @Nullable Calendar referenceTime,
+ SmartSelection.ClassificationResult classification,
+ String text) {
+ final String type = classification.mCollection.trim().toLowerCase(Locale.ENGLISH);
text = text.trim();
switch (type) {
case TextClassifier.TYPE_EMAIL:
- intents.add(new Intent(Intent.ACTION_SENDTO)
- .setData(Uri.parse(String.format("mailto:%s", text))));
- intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
+ return createForEmail(text);
+ case TextClassifier.TYPE_PHONE:
+ return createForPhone(text);
+ case TextClassifier.TYPE_ADDRESS:
+ return createForAddress(text);
+ case TextClassifier.TYPE_URL:
+ return createForUrl(context, text);
+ case TextClassifier.TYPE_DATE:
+ case TextClassifier.TYPE_DATE_TIME:
+ if (classification.mDatetime != null) {
+ Calendar eventTime = Calendar.getInstance();
+ eventTime.setTimeInMillis(classification.mDatetime.mMsSinceEpoch);
+ return createForDatetime(type, referenceTime, eventTime);
+ } else {
+ return new ArrayList<>();
+ }
+ case TextClassifier.TYPE_FLIGHT_NUMBER:
+ return createForFlight(text);
+ default:
+ return new ArrayList<>();
+ }
+ }
+
+ @NonNull
+ private static List<Intent> createForEmail(String text) {
+ return Arrays.asList(
+ new Intent(Intent.ACTION_SENDTO)
+ .setData(Uri.parse(String.format("mailto:%s", text))),
+ new Intent(Intent.ACTION_INSERT_OR_EDIT)
.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
.putExtra(ContactsContract.Intents.Insert.EMAIL, text));
- break;
- case TextClassifier.TYPE_PHONE:
- intents.add(new Intent(Intent.ACTION_DIAL)
- .setData(Uri.parse(String.format("tel:%s", text))));
- intents.add(new Intent(Intent.ACTION_INSERT_OR_EDIT)
+ }
+
+ @NonNull
+ private static List<Intent> createForPhone(String text) {
+ return Arrays.asList(
+ new Intent(Intent.ACTION_DIAL)
+ .setData(Uri.parse(String.format("tel:%s", text))),
+ new Intent(Intent.ACTION_INSERT_OR_EDIT)
.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
- .putExtra(ContactsContract.Intents.Insert.PHONE, text));
- intents.add(new Intent(Intent.ACTION_SENDTO)
+ .putExtra(ContactsContract.Intents.Insert.PHONE, text),
+ new Intent(Intent.ACTION_SENDTO)
.setData(Uri.parse(String.format("smsto:%s", text))));
- break;
- case TextClassifier.TYPE_ADDRESS:
- intents.add(new Intent(Intent.ACTION_VIEW)
- .setData(Uri.parse(String.format("geo:0,0?q=%s", text))));
- break;
- case TextClassifier.TYPE_URL:
- final String httpPrefix = "http://";
- final String httpsPrefix = "https://";
- if (text.toLowerCase().startsWith(httpPrefix)) {
- text = httpPrefix + text.substring(httpPrefix.length());
- } else if (text.toLowerCase().startsWith(httpsPrefix)) {
- text = httpsPrefix + text.substring(httpsPrefix.length());
- } else {
- text = httpPrefix + text;
- }
- intents.add(new Intent(Intent.ACTION_VIEW, Uri.parse(text))
- .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()));
- break;
+ }
+
+ @NonNull
+ private static List<Intent> createForAddress(String text) {
+ return Arrays.asList(new Intent(Intent.ACTION_VIEW)
+ .setData(Uri.parse(String.format("geo:0,0?q=%s", text))));
+ }
+
+ @NonNull
+ private static List<Intent> createForUrl(Context context, String text) {
+ final String httpPrefix = "http://";
+ final String httpsPrefix = "https://";
+ if (text.toLowerCase().startsWith(httpPrefix)) {
+ text = httpPrefix + text.substring(httpPrefix.length());
+ } else if (text.toLowerCase().startsWith(httpsPrefix)) {
+ text = httpsPrefix + text.substring(httpsPrefix.length());
+ } else {
+ text = httpPrefix + text;
+ }
+ return Arrays.asList(new Intent(Intent.ACTION_VIEW, Uri.parse(text))
+ .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()));
+ }
+
+ @NonNull
+ private static List<Intent> createForDatetime(
+ String type, @Nullable Calendar referenceTime, Calendar eventTime) {
+ if (referenceTime == null) {
+ // If no reference time was given, use now.
+ referenceTime = Calendar.getInstance();
+ }
+ List<Intent> intents = new ArrayList<>();
+ intents.add(createCalendarViewIntent(eventTime));
+ final long millisSinceReference =
+ eventTime.getTimeInMillis() - referenceTime.getTimeInMillis();
+ if (millisSinceReference > MIN_EVENT_FUTURE_MILLIS) {
+ intents.add(createCalendarCreateEventIntent(eventTime, type));
}
return intents;
}
+ @NonNull
+ private static List<Intent> createForFlight(String text) {
+ return Arrays.asList(new Intent(Intent.ACTION_WEB_SEARCH)
+ .putExtra(SearchManager.QUERY, text));
+ }
+
+ @NonNull
+ private static Intent createCalendarViewIntent(Calendar eventTime) {
+ Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
+ builder.appendPath("time");
+ ContentUris.appendId(builder, eventTime.getTimeInMillis());
+ return new Intent(Intent.ACTION_VIEW).setData(builder.build());
+ }
+
+ @NonNull
+ private static Intent createCalendarCreateEventIntent(
+ Calendar eventTime, @EntityType String type) {
+ final boolean isAllDay = TextClassifier.TYPE_DATE.equals(type);
+ return new Intent(Intent.ACTION_INSERT)
+ .setData(CalendarContract.Events.CONTENT_URI)
+ .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay)
+ .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, eventTime.getTimeInMillis())
+ .putExtra(CalendarContract.EXTRA_EVENT_END_TIME,
+ eventTime.getTimeInMillis() + DEFAULT_EVENT_DURATION);
+ }
+
@Nullable
public static String getLabel(Context context, @Nullable Intent intent) {
if (intent == null || intent.getAction() == null) {
return null;
}
+ final String authority =
+ intent.getData() == null ? null : intent.getData().getAuthority();
switch (intent.getAction()) {
case Intent.ACTION_DIAL:
return context.getString(com.android.internal.R.string.dial);
@@ -578,6 +649,11 @@
default:
return null;
}
+ case Intent.ACTION_INSERT:
+ if (CalendarContract.AUTHORITY.equals(authority)) {
+ return context.getString(com.android.internal.R.string.add_calendar_event);
+ }
+ return null;
case Intent.ACTION_INSERT_OR_EDIT:
switch (intent.getDataString()) {
case ContactsContract.Contacts.CONTENT_ITEM_TYPE:
@@ -586,6 +662,9 @@
return null;
}
case Intent.ACTION_VIEW:
+ if (CalendarContract.AUTHORITY.equals(authority)) {
+ return context.getString(com.android.internal.R.string.view_calendar);
+ }
switch (intent.getScheme()) {
case "geo":
return context.getString(com.android.internal.R.string.map);
@@ -595,6 +674,8 @@
default:
return null;
}
+ case Intent.ACTION_WEB_SEARCH:
+ return context.getString(com.android.internal.R.string.view_flight);
default:
return null;
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 90cc481..cba11a8 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -931,12 +931,12 @@
* Note that this setting affects only JavaScript access to file scheme
* resources. Other access to such resources, for example, from image HTML
* elements, is unaffected. To prevent possible violation of same domain policy
- * on {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} and earlier
- * devices, you should explicitly set this value to {@code false}.
+ * when targeting {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and earlier,
+ * you should explicitly set this value to {@code false}.
* <p>
- * The default value is {@code true} for API level
+ * The default value is {@code true} for apps targeting
* {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below,
- * and {@code false} for API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
+ * and {@code false} when targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
* and above.
*
* @param flag whether JavaScript running in the context of a file scheme
@@ -947,18 +947,18 @@
/**
* Sets whether JavaScript running in the context of a file scheme URL
* should be allowed to access content from other file scheme URLs. To
- * enable the most restrictive, and therefore secure policy, this setting
+ * enable the most restrictive, and therefore secure, policy this setting
* should be disabled. Note that the value of this setting is ignored if
* the value of {@link #getAllowUniversalAccessFromFileURLs} is {@code true}.
* Note too, that this setting affects only JavaScript access to file scheme
* resources. Other access to such resources, for example, from image HTML
* elements, is unaffected. To prevent possible violation of same domain policy
- * on {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} and earlier
- * devices, you should explicitly set this value to {@code false}.
+ * when targeting {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and earlier,
+ * you should explicitly set this value to {@code false}.
* <p>
- * The default value is {@code true} for API level
+ * The default value is {@code true} for apps targeting
* {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below,
- * and {@code false} for API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
+ * and {@code false} when targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
* and above.
*
* @param flag whether JavaScript running in the context of a file scheme
@@ -1394,26 +1394,26 @@
/**
- * Sets whether Safe Browsing is enabled. Safe browsing allows WebView to
+ * Sets whether Safe Browsing is enabled. Safe Browsing allows WebView to
* protect against malware and phishing attacks by verifying the links.
*
* <p>
- * Safe browsing is disabled by default. The recommended way to enable Safe browsing is using a
- * manifest tag to change the default value to enabled for all WebViews (read <a
- * href="{@docRoot}reference/android/webkit/WebView.html">general Safe Browsing info</a>).
+ * Safe Browsing can be disabled for all WebViews using a manifest tag (read <a
+ * href="{@docRoot}reference/android/webkit/WebView.html">general Safe Browsing info</a>). The
+ * manifest tag has a lower precedence than this API.
*
* <p>
- * This API overrides the manifest tag value for this WebView.
+ * Safe Browsing is enabled by default for devices which support it.
*
- * @param enabled Whether Safe browsing is enabled.
+ * @param enabled Whether Safe Browsing is enabled.
*/
public abstract void setSafeBrowsingEnabled(boolean enabled);
/**
- * Gets whether Safe browsing is enabled.
+ * Gets whether Safe Browsing is enabled.
* See {@link #setSafeBrowsingEnabled}.
*
- * @return {@code true} if Safe browsing is enabled and {@code false} otherwise.
+ * @return {@code true} if Safe Browsing is enabled and {@code false} otherwise.
*/
public abstract boolean getSafeBrowsingEnabled();
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 2c51ee9..d2cb70e 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -327,22 +327,25 @@
* <h3>Safe Browsing</h3>
*
* <p>
- * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the
- * user to allow them to navigate back safely or proceed to the malicious page.
+ * With Safe Browsing, WebView will block malicious URLs and present a warning UI to the user to
+ * allow them to navigate back safely or proceed to the malicious page.
* <p>
- * The recommended way for apps to enable the feature is putting the following tag in the manifest's
- * {@code <application>} element:
+ * Safe Browsing is enabled by default on devices which support it. If your app needs to disable
+ * Safe Browsing for all WebViews, it can do so in the manifest's {@code <application>} element:
* <p>
* <pre>
* <manifest>
* <application>
* ...
* <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
- * android:value="true" />
+ * android:value="false" />
* </application>
* </manifest>
* </pre>
*
+ * <p>
+ * Otherwise, see {@link WebSettings#setSafeBrowsingEnabled}.
+ *
*/
// Implementation notes.
// The WebView is a thin API class that delegates its public API to a backend WebViewProvider
@@ -1670,9 +1673,8 @@
* invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
* devices {@code callback} will receive {@code false}.
* <p>
- * This does not enable the Safe Browsing feature itself, and should only be called if Safe
- * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This
- * prepares resources used for Safe Browsing.
+ * This should not be called if Safe Browsing has been disabled by manifest tag or {@link
+ * WebSettings#setSafeBrowsingEnabled}. This prepares resources used for Safe Browsing.
* <p>
* This should be called with the Application Context (and will always use the Application
* context to do its work regardless).
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6bee58f..594d240 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -19,6 +19,7 @@
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -30,6 +31,7 @@
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
+import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.StrictMode;
@@ -2744,7 +2746,7 @@
}
private void drawSelector(Canvas canvas) {
- if (!mSelectorRect.isEmpty()) {
+ if (shouldDrawSelector()) {
final Drawable selector = mSelector;
selector.setBounds(mSelectorRect);
selector.draw(canvas);
@@ -2752,6 +2754,14 @@
}
/**
+ * @hide
+ */
+ @TestApi
+ public final boolean shouldDrawSelector() {
+ return !mSelectorRect.isEmpty();
+ }
+
+ /**
* Controls whether the selection highlight drawable should be drawn on top of the item or
* behind it.
*
@@ -6026,6 +6036,11 @@
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
return getTarget().commitContent(inputContentInfo, flags, opts);
}
+
+ @Override
+ public void reportLanguageHint(@NonNull LocaleList languageHint) {
+ getTarget().reportLanguageHint(languageHint);
+ }
}
/**
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index d3e807d..514ff76 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -91,8 +91,7 @@
void noteVibratorOn(int uid, long durationMillis);
void noteVibratorOff(int uid);
- void noteStartGps(int uid);
- void noteStopGps(int uid);
+ void noteGpsChanged(in WorkSource oldSource, in WorkSource newSource);
void noteGpsSignalQuality(int signalLevel);
void noteScreenState(int state);
void noteScreenBrightness(int brightness);
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 2b0b5ee..d247013 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -146,7 +146,11 @@
private String getLangScriptKey() {
if (mLangScriptKey == null) {
- Locale parentWithScript = getParent(LocaleHelper.addLikelySubtags(mLocale));
+ Locale baseLocale = new Locale.Builder()
+ .setLocale(mLocale)
+ .setExtension(Locale.UNICODE_LOCALE_EXTENSION, "")
+ .build();
+ Locale parentWithScript = getParent(LocaleHelper.addLikelySubtags(baseLocale));
mLangScriptKey =
(parentWithScript == null)
? mLocale.toLanguageTag()
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 147438c..f8117a7 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -306,4 +306,13 @@
* operation will immediately be finished with no further attempts to restore app data.
*/
int abortFullRestore();
+
+ /**
+ * Returns flags with additional information about the transport, which is accessible to the
+ * {@link android.app.backup.BackupAgent}. This allows the agent to decide what to backup or
+ * restore based on properties of the transport.
+ *
+ * <p>For supported flags see {@link android.app.backup.BackupAgent}.
+ */
+ int getTransportFlags();
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 51f51c2..ee3bec8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -4587,8 +4587,35 @@
int mGpsNesting;
- public void noteStartGpsLocked(int uid) {
- uid = mapUid(uid);
+ public void noteGpsChangedLocked(WorkSource oldWs, WorkSource newWs) {
+ for (int i = 0; i < newWs.size(); ++i) {
+ noteStartGpsLocked(newWs.get(i), null);
+ }
+
+ for (int i = 0; i < oldWs.size(); ++i) {
+ noteStopGpsLocked((oldWs.get(i)), null);
+ }
+
+ List<WorkChain>[] wcs = WorkSource.diffChains(oldWs, newWs);
+ if (wcs != null) {
+ if (wcs[0] != null) {
+ final List<WorkChain> newChains = wcs[0];
+ for (int i = 0; i < newChains.size(); ++i) {
+ noteStartGpsLocked(-1, newChains.get(i));
+ }
+ }
+
+ if (wcs[1] != null) {
+ final List<WorkChain> goneChains = wcs[1];
+ for (int i = 0; i < goneChains.size(); ++i) {
+ noteStopGpsLocked(-1, goneChains.get(i));
+ }
+ }
+ }
+ }
+
+ private void noteStartGpsLocked(int uid, WorkChain workChain) {
+ uid = getAttributionUid(uid, workChain);
final long elapsedRealtime = mClocks.elapsedRealtime();
final long uptime = mClocks.uptimeMillis();
if (mGpsNesting == 0) {
@@ -4598,11 +4625,19 @@
addHistoryRecordLocked(elapsedRealtime, uptime);
}
mGpsNesting++;
+
+ if (workChain == null) {
+ StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, uid, null, 1);
+ } else {
+ StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED,
+ workChain.getUids(), workChain.getTags(), 1);
+ }
+
getUidStatsLocked(uid).noteStartGps(elapsedRealtime);
}
- public void noteStopGpsLocked(int uid) {
- uid = mapUid(uid);
+ private void noteStopGpsLocked(int uid, WorkChain workChain) {
+ uid = getAttributionUid(uid, workChain);
final long elapsedRealtime = mClocks.elapsedRealtime();
final long uptime = mClocks.uptimeMillis();
mGpsNesting--;
@@ -4614,6 +4649,14 @@
stopAllGpsSignalQualityTimersLocked(-1);
mGpsSignalQualityBin = -1;
}
+
+ if (workChain == null) {
+ StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, uid, null, 0);
+ } else {
+ StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, workChain.getUids(),
+ workChain.getTags(), 0);
+ }
+
getUidStatsLocked(uid).noteStopGps(elapsedRealtime);
}
@@ -9842,9 +9885,7 @@
public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
t.startRunningLocked(elapsedRealtimeMs);
- if (sensor == Sensor.GPS) {
- StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), null, 1);
- } else {
+ if (sensor != Sensor.GPS) {
StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null, sensor,
1);
}
@@ -9855,14 +9896,9 @@
DualTimer t = getSensorTimerLocked(sensor, false);
if (t != null) {
t.stopRunningLocked(elapsedRealtimeMs);
- if (!t.isRunningLocked()) { // only tell statsd if truly stopped
- if (sensor == Sensor.GPS) {
- StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), null,
- 0);
- } else {
- StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null,
- sensor, 0);
- }
+ if (sensor != Sensor.GPS) {
+ StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null,
+ sensor, 0);
}
}
}
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 079ba0b..f9a2341 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -53,6 +53,8 @@
public static final int DISABLE_VERIFIER = 1 << 9;
/** Only use oat files located in /system. Otherwise use dex/jar/apk . */
public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10;
+ /** Do not enfore hidden API access restrictions. */
+ public static final int DISABLE_HIDDEN_API_CHECKS = 1 << 11;
/** No external storage should be mounted. */
public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
@@ -156,6 +158,9 @@
*/
public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
+ // SystemServer is always allowed to use hidden APIs.
+ runtimeFlags |= DISABLE_HIDDEN_API_CHECKS;
+
VM_HOOKS.preFork();
// Resets nice priority for zygote process.
resetNicePriority();
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 5659470..39279b5 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -583,7 +583,7 @@
installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName,
instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter,
uuid, classLoaderContext, seInfo, false /* downgrade */,
- targetSdkVersion);
+ targetSdkVersion, /*profileName*/ null);
} catch (RemoteException | ServiceSpecificException e) {
// Ignore (but log), we need this on the classpath for fallback mode.
Log.w(TAG, "Failed compiling classpath element for system server: "
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 28291ae..e08caa8 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -23,6 +23,7 @@
import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Handler;
+import android.os.LocaleList;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -64,6 +65,7 @@
private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;
private static final int DO_CLOSE_CONNECTION = 150;
private static final int DO_COMMIT_CONTENT = 160;
+ private static final int DO_REPORT_LANGUAGE_HINT = 170;
@GuardedBy("mLock")
@Nullable
@@ -217,6 +219,10 @@
callback));
}
+ public void reportLanguageHint(@NonNull LocaleList languageHint) {
+ dispatchMessage(obtainMessageO(DO_REPORT_LANGUAGE_HINT, languageHint));
+ }
+
void dispatchMessage(Message msg) {
// If we are calling this from the main thread, then we can call
// right through. Otherwise, we need to send the message to the
@@ -577,6 +583,16 @@
}
return;
}
+ case DO_REPORT_LANGUAGE_HINT: {
+ final LocaleList languageHint = (LocaleList) msg.obj;
+ final InputConnection ic = getInputConnection();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "reportLanguageHint on inactive InputConnection");
+ return;
+ }
+ ic.reportLanguageHint(languageHint);
+ return;
+ }
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index c227991..e69a87ff 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -17,6 +17,7 @@
package com.android.internal.view;
import android.os.Bundle;
+import android.os.LocaleList;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
@@ -78,4 +79,6 @@
void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, int sec,
IInputContextCallback callback);
+
+ void reportLanguageHint(in LocaleList languageHint);
}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 5b65bbe..34be598 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -22,6 +22,7 @@
import android.inputmethodservice.AbstractInputMethodService;
import android.os.Bundle;
import android.os.Handler;
+import android.os.LocaleList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
@@ -620,6 +621,14 @@
}
@AnyThread
+ public void reportLanguageHint(@NonNull LocaleList languageHint) {
+ try {
+ mIInputContext.reportLanguageHint(languageHint);
+ } catch (RemoteException e) {
+ }
+ }
+
+ @AnyThread
private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
return (mMissingMethods & methodFlag) == methodFlag;
}
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 6af41a5..324f923 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -256,7 +256,7 @@
final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity,
mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
if (hgrav == Gravity.RIGHT) {
- xOffset += mAnchorView.getWidth();
+ xOffset -= mAnchorView.getWidth();
}
popup.setHorizontalOffset(xOffset);
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index d9ca5be..445379b 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -263,7 +263,6 @@
mShownAnchorView, mOverflowOnly, mPopupStyleAttr, mPopupStyleRes);
subPopup.setPresenterCallback(mPresenterCallback);
subPopup.setForceShowIcon(MenuPopup.shouldPreserveIconSpacing(subMenu));
- subPopup.setGravity(mDropDownGravity);
// Pass responsibility for handling onDismiss to the submenu.
subPopup.setOnDismissListener(mOnDismissListener);
@@ -273,8 +272,17 @@
mMenu.close(false /* closeAllMenus */);
// Show the new sub-menu popup at the same location as this popup.
- final int horizontalOffset = mPopup.getHorizontalOffset();
+ int horizontalOffset = mPopup.getHorizontalOffset();
final int verticalOffset = mPopup.getVerticalOffset();
+
+ // As xOffset of parent menu popup is subtracted with Anchor width for Gravity.RIGHT,
+ // So, again to display sub-menu popup in same xOffset, add the Anchor width.
+ final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity,
+ mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if (hgrav == Gravity.RIGHT) {
+ horizontalOffset += mAnchorView.getWidth();
+ }
+
if (subPopup.tryShow(horizontalOffset, verticalOffset)) {
if (mPresenterCallback != null) {
mPresenterCallback.onOpenSubMenu(subMenu);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 47765d9..5751fc9 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -110,8 +110,8 @@
"android_util_AssetManager.cpp",
"android_util_Binder.cpp",
"android_util_EventLog.cpp",
- "android_util_MemoryIntArray.cpp",
"android_util_Log.cpp",
+ "android_util_MemoryIntArray.cpp",
"android_util_PathParser.cpp",
"android_util_Process.cpp",
"android_util_StringBlock.cpp",
@@ -163,6 +163,7 @@
"android_media_AudioTrack.cpp",
"android_media_DeviceCallback.cpp",
"android_media_JetPlayer.cpp",
+ "android_media_MediaMetricsJNI.cpp",
"android_media_RemoteDisplay.cpp",
"android_media_ToneGenerator.cpp",
"android_hardware_Camera.cpp",
@@ -189,6 +190,7 @@
"android_backup_FileBackupHelperBase.cpp",
"android_backup_BackupHelperDispatcher.cpp",
"android_app_backup_FullBackup.cpp",
+ "android_content_res_ApkAssets.cpp",
"android_content_res_ObbScanner.cpp",
"android_content_res_Configuration.cpp",
"android_animation_PropertyValuesHolder.cpp",
@@ -260,6 +262,7 @@
"libselinux",
"libicuuc",
"libmedia",
+ "libmediametrics",
"libaudioclient",
"libjpeg",
"libusbhost",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aa9a824..e3b5c8f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -122,6 +122,7 @@
extern int register_android_util_PathParser(JNIEnv* env);
extern int register_android_content_StringBlock(JNIEnv* env);
extern int register_android_content_XmlBlock(JNIEnv* env);
+extern int register_android_content_res_ApkAssets(JNIEnv* env);
extern int register_android_graphics_Canvas(JNIEnv* env);
extern int register_android_graphics_CanvasProperty(JNIEnv* env);
extern int register_android_graphics_ColorFilter(JNIEnv* env);
@@ -1340,6 +1341,7 @@
REG_JNI(register_android_content_AssetManager),
REG_JNI(register_android_content_StringBlock),
REG_JNI(register_android_content_XmlBlock),
+ REG_JNI(register_android_content_res_ApkAssets),
REG_JNI(register_android_text_AndroidCharacter),
REG_JNI(register_android_text_Hyphenator),
REG_JNI(register_android_text_MeasuredParagraph),
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
index 8b3ce66..262b553 100644
--- a/core/jni/android/graphics/AnimatedImageDrawable.cpp
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -72,7 +72,6 @@
}
sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(animatedImg));
- drawable->start();
return reinterpret_cast<jlong>(drawable.release());
}
@@ -114,9 +113,9 @@
return drawable->isRunning();
}
-static void AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
- drawable->start();
+ return drawable->start();
}
static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
@@ -138,7 +137,7 @@
{ "nGetAlpha", "(J)I", (void*) AnimatedImageDrawable_nGetAlpha },
{ "nSetColorFilter", "(JJ)V", (void*) AnimatedImageDrawable_nSetColorFilter },
{ "nIsRunning", "(J)Z", (void*) AnimatedImageDrawable_nIsRunning },
- { "nStart", "(J)V", (void*) AnimatedImageDrawable_nStart },
+ { "nStart", "(J)Z", (void*) AnimatedImageDrawable_nStart },
{ "nStop", "(J)V", (void*) AnimatedImageDrawable_nStop },
{ "nNativeByteSize", "(J)J", (void*) AnimatedImageDrawable_nNativeByteSize },
};
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index dd3e6f0..c50026e 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -28,7 +28,7 @@
#include <nativehelper/ScopedUtfChars.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_util_AssetManager.h>
-#include <androidfw/AssetManager.h>
+#include <androidfw/AssetManager2.h>
#include "Utils.h"
#include "FontUtils.h"
@@ -224,7 +224,8 @@
NPE_CHECK_RETURN_ZERO(env, jpath);
NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
- AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr);
+
+ Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(env, jassetMgr);
if (NULL == mgr) {
builder->axes.clear();
return false;
@@ -236,27 +237,33 @@
return false;
}
- Asset* asset;
- if (isAsset) {
- asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
- } else {
- asset = cookie ? mgr->openNonAsset(static_cast<int32_t>(cookie), str.c_str(),
- Asset::ACCESS_BUFFER) : mgr->openNonAsset(str.c_str(), Asset::ACCESS_BUFFER);
+ std::unique_ptr<Asset> asset;
+ {
+ ScopedLock<AssetManager2> locked_mgr(*mgr);
+ if (isAsset) {
+ asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER);
+ } else if (cookie > 0) {
+ // Valid java cookies are 1-based, but AssetManager cookies are 0-based.
+ asset = locked_mgr->OpenNonAsset(str.c_str(), static_cast<ApkAssetsCookie>(cookie - 1),
+ Asset::ACCESS_BUFFER);
+ } else {
+ asset = locked_mgr->OpenNonAsset(str.c_str(), Asset::ACCESS_BUFFER);
+ }
}
- if (NULL == asset) {
+ if (nullptr == asset) {
builder->axes.clear();
return false;
}
const void* buf = asset->getBuffer(false);
if (NULL == buf) {
- delete asset;
builder->axes.clear();
return false;
}
- sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, asset));
+ sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset,
+ asset.release()));
return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic);
}
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 1e7f5f5..49cbb54 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -307,7 +307,8 @@
static void getTextPath(JNIEnv* env, Paint* paint, const Typeface* typeface, const jchar* text,
jint count, jint bidiFlags, jfloat x, jfloat y, SkPath* path) {
minikin::Layout layout = MinikinUtils::doLayout(
- paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count);
+ paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count,
+ nullptr, 0);
size_t nGlyphs = layout.nGlyphs();
uint16_t* glyphs = new uint16_t[nGlyphs];
SkPoint* pos = new SkPoint[nGlyphs];
@@ -349,7 +350,8 @@
SkIRect ir;
minikin::Layout layout = MinikinUtils::doLayout(&paint,
- static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count);
+ static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0, count, count, nullptr,
+ 0);
minikin::MinikinRect rect;
layout.getBounds(&rect);
r.fLeft = rect.mLeft;
@@ -465,7 +467,7 @@
}
minikin::Layout layout = MinikinUtils::doLayout(paint,
static_cast<minikin::Bidi>(bidiFlags), typeface, str.get(), 0, str.size(),
- str.size());
+ str.size(), nullptr, 0);
size_t nGlyphs = countNonSpaceGlyphs(layout);
if (nGlyphs != 1 && nChars > 1) {
// multiple-character input, and was not a ligature
@@ -485,7 +487,8 @@
// U+1F1FF (REGIONAL INDICATOR SYMBOL LETTER Z) is \uD83C\uDDFF in UTF16.
static const jchar ZZ_FLAG_STR[] = { 0xD83C, 0xDDFF, 0xD83C, 0xDDFF };
minikin::Layout zzLayout = MinikinUtils::doLayout(paint,
- static_cast<minikin::Bidi>(bidiFlags), typeface, ZZ_FLAG_STR, 0, 4, 4);
+ static_cast<minikin::Bidi>(bidiFlags), typeface, ZZ_FLAG_STR, 0, 4, 4,
+ nullptr, 0);
if (zzLayout.nGlyphs() != 1 || layoutContainsNotdef(zzLayout)) {
// The font collection doesn't have a glyph for unknown flag. Just return true.
return true;
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 09e37e1..49a24a3 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -361,7 +361,7 @@
code->sdkVersion = sdkVersion;
code->javaAssetManager = env->NewGlobalRef(jAssetMgr);
- code->assetManager = assetManagerForJavaObject(env, jAssetMgr);
+ code->assetManager = NdkAssetManagerForJavaObject(env, jAssetMgr);
if (obbDir != NULL) {
dirStr = env->GetStringUTFChars(obbDir, NULL);
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
new file mode 100644
index 0000000..c0f151b
--- /dev/null
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "android-base/unique_fd.h"
+#include "androidfw/ApkAssets.h"
+#include "utils/misc.h"
+
+#include "core_jni_helpers.h"
+#include "jni.h"
+#include "nativehelper/ScopedUtfChars.h"
+
+using ::android::base::unique_fd;
+
+namespace android {
+
+static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
+ jboolean force_shared_lib, jboolean overlay) {
+ ScopedUtfChars path(env, java_path);
+ if (path.c_str() == nullptr) {
+ return 0;
+ }
+
+ std::unique_ptr<const ApkAssets> apk_assets;
+ if (overlay) {
+ apk_assets = ApkAssets::LoadOverlay(path.c_str(), system);
+ } else if (force_shared_lib) {
+ apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system);
+ } else {
+ apk_assets = ApkAssets::Load(path.c_str(), system);
+ }
+
+ if (apk_assets == nullptr) {
+ std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
+ jstring friendly_name, jboolean system, jboolean force_shared_lib) {
+ ScopedUtfChars friendly_name_utf8(env, friendly_name);
+ if (friendly_name_utf8.c_str() == nullptr) {
+ return 0;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+ if (fd < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+ return 0;
+ }
+
+ unique_fd dup_fd(::dup(fd));
+ if (dup_fd < 0) {
+ jniThrowIOException(env, errno);
+ return 0;
+ }
+
+ std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd),
+ friendly_name_utf8.c_str(),
+ system, force_shared_lib);
+ if (apk_assets == nullptr) {
+ std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d",
+ friendly_name_utf8.c_str(), dup_fd.get());
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+ delete reinterpret_cast<ApkAssets*>(ptr);
+}
+
+static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+ const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+ return env->NewStringUTF(apk_assets->GetPath().c_str());
+}
+
+static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+ const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+ return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
+}
+
+static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+ const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+ (void)apk_assets;
+ return JNI_TRUE;
+}
+
+static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
+ ScopedUtfChars path_utf8(env, file_name);
+ if (path_utf8.c_str() == nullptr) {
+ return 0;
+ }
+
+ const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+ std::unique_ptr<Asset> asset = apk_assets->Open(path_utf8.c_str(),
+ Asset::AccessMode::ACCESS_RANDOM);
+ if (asset == nullptr) {
+ jniThrowException(env, "java/io/FileNotFoundException", path_utf8.c_str());
+ return 0;
+ }
+
+ // DynamicRefTable is only needed when looking up resource references. Opening an XML file
+ // directly from an ApkAssets has no notion of proper resource references.
+ std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(nullptr /*dynamicRefTable*/);
+ status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
+ asset.reset();
+
+ if (err != NO_ERROR) {
+ jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+ return 0;
+ }
+ return reinterpret_cast<jlong>(xml_tree.release());
+}
+
+// JNI registration.
+static const JNINativeMethod gApkAssetsMethods[] = {
+ {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad},
+ {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J",
+ (void*)NativeLoadFromFd},
+ {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+ {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
+ {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
+ {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
+ {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
+};
+
+int register_android_content_res_ApkAssets(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods,
+ arraysize(gApkAssetsMethods));
+}
+
+} // namespace android
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index f08b89c..6b961f5 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -30,6 +30,10 @@
#include "SkRegion.h"
#include "SkVertices.h"
+namespace minikin {
+class MeasuredText;
+} // namespace minikin
+
namespace android {
namespace CanvasJNI {
@@ -480,7 +484,7 @@
const Typeface* typeface = paint->getAndroidTypeface();
jchar* jchars = env->GetCharArrayElements(text, NULL);
get_canvas(canvasHandle)->drawText(jchars + index, 0, count, count, x, y,
- static_cast<minikin::Bidi>(bidiFlags), *paint, typeface);
+ static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr, 0);
env->ReleaseCharArrayElements(text, jchars, JNI_ABORT);
}
@@ -492,20 +496,22 @@
const int count = end - start;
const jchar* jchars = env->GetStringChars(text, NULL);
get_canvas(canvasHandle)->drawText(jchars + start, 0, count, count, x, y,
- static_cast<minikin::Bidi>(bidiFlags), *paint, typeface);
+ static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr, 0);
env->ReleaseStringChars(text, jchars);
}
static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, jint index,
jint count, jint contextIndex, jint contextCount, jfloat x, jfloat y,
- jboolean isRtl, jlong paintHandle) {
+ jboolean isRtl, jlong paintHandle, jlong mtHandle, jint mtOffset) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
+ minikin::MeasuredText* mt = reinterpret_cast<minikin::MeasuredText*>(mtHandle);
const Typeface* typeface = paint->getAndroidTypeface();
const minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
jchar* jchars = env->GetCharArrayElements(text, NULL);
get_canvas(canvasHandle)->drawText(jchars + contextIndex, index - contextIndex, count,
- contextCount, x, y, bidiFlags, *paint, typeface);
+ contextCount, x, y, bidiFlags, *paint, typeface, mt,
+ mtOffset);
env->ReleaseCharArrayElements(text, jchars, JNI_ABORT);
}
@@ -520,7 +526,7 @@
jint contextCount = contextEnd - contextStart;
const jchar* jchars = env->GetStringChars(text, NULL);
get_canvas(canvasHandle)->drawText(jchars + contextStart, start - contextStart, count,
- contextCount, x, y, bidiFlags, *paint, typeface);
+ contextCount, x, y, bidiFlags, *paint, typeface, nullptr, 0);
env->ReleaseStringChars(text, jchars);
}
@@ -628,7 +634,7 @@
{"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
{"nDrawText","(J[CIIFFIJ)V", (void*) CanvasJNI::drawTextChars},
{"nDrawText","(JLjava/lang/String;IIFFIJ)V", (void*) CanvasJNI::drawTextString},
- {"nDrawTextRun","(J[CIIIIFFZJ)V", (void*) CanvasJNI::drawTextRunChars},
+ {"nDrawTextRun","(J[CIIIIFFZJJI)V", (void*) CanvasJNI::drawTextRunChars},
{"nDrawTextRun","(JLjava/lang/String;IIIIFFZJ)V", (void*) CanvasJNI::drawTextRunString},
{"nDrawTextOnPath","(J[CIIJFFIJ)V", (void*) CanvasJNI::drawTextOnPathChars},
{"nDrawTextOnPath","(JLjava/lang/String;JFFIJ)V", (void*) CanvasJNI::drawTextOnPathString},
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index e4da3c6..ebd16c7 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -31,6 +31,7 @@
#include "android_media_AudioFormat.h"
#include "android_media_AudioErrors.h"
#include "android_media_DeviceCallback.h"
+#include "android_media_MediaMetricsJNI.h"
// ----------------------------------------------------------------------------
@@ -751,6 +752,39 @@
}
// ----------------------------------------------------------------------------
+static jobject
+android_media_AudioRecord_native_getMetrics(JNIEnv *env, jobject thiz)
+{
+ ALOGV("android_media_AudioRecord_native_getMetrics");
+
+ sp<AudioRecord> lpRecord = getAudioRecord(env, thiz);
+
+ if (lpRecord == NULL) {
+ ALOGE("Unable to retrieve AudioRecord pointer for getMetrics()");
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return (jobject) NULL;
+ }
+
+ // get what we have for the metrics from the record session
+ MediaAnalyticsItem *item = NULL;
+
+ status_t err = lpRecord->getMetrics(item);
+ if (err != OK) {
+ ALOGE("getMetrics failed");
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return (jobject) NULL;
+ }
+
+ jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL /* mybundle */);
+
+ // housekeeping
+ delete item;
+ item = NULL;
+
+ return mybundle;
+}
+
+// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
// name, signature, funcPtr
@@ -781,6 +815,8 @@
"()I", (void *)android_media_AudioRecord_get_pos_update_period},
{"native_get_min_buff_size",
"(III)I", (void *)android_media_AudioRecord_get_min_buff_size},
+ {"native_getMetrics", "()Landroid/os/PersistableBundle;",
+ (void *)android_media_AudioRecord_native_getMetrics},
{"native_setInputDevice", "(I)Z", (void *)android_media_AudioRecord_setInputDevice},
{"native_getRoutedDeviceId", "()I", (void *)android_media_AudioRecord_getRoutedDeviceId},
{"native_enableDeviceCallback", "()V", (void *)android_media_AudioRecord_enableDeviceCallback},
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index 11011b1..afbc579 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -34,6 +34,7 @@
#include "android_media_AudioFormat.h"
#include "android_media_AudioErrors.h"
+#include "android_media_MediaMetricsJNI.h"
#include "android_media_PlaybackParams.h"
#include "android_media_DeviceCallback.h"
#include "android_media_VolumeShaper.h"
@@ -1011,6 +1012,39 @@
return (jint) nativeToJavaStatus(status);
}
+// ----------------------------------------------------------------------------
+static jobject
+android_media_AudioTrack_native_getMetrics(JNIEnv *env, jobject thiz)
+{
+ ALOGD("android_media_AudioTrack_native_getMetrics");
+
+ sp<AudioTrack> lpTrack = getAudioTrack(env, thiz);
+
+ if (lpTrack == NULL) {
+ ALOGE("Unable to retrieve AudioTrack pointer for getMetrics()");
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return (jobject) NULL;
+ }
+
+ // get what we have for the metrics from the track
+ MediaAnalyticsItem *item = NULL;
+
+ status_t err = lpTrack->getMetrics(item);
+ if (err != OK) {
+ ALOGE("getMetrics failed");
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return (jobject) NULL;
+ }
+
+ jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL /* mybundle */);
+
+ // housekeeping
+ delete item;
+ item = NULL;
+
+ return mybundle;
+}
+
// ----------------------------------------------------------------------------
static jint android_media_AudioTrack_set_loop(JNIEnv *env, jobject thiz,
@@ -1275,6 +1309,8 @@
{"native_get_underrun_count", "()I", (void *)android_media_AudioTrack_get_underrun_count},
{"native_get_flags", "()I", (void *)android_media_AudioTrack_get_flags},
{"native_get_timestamp", "([J)I", (void *)android_media_AudioTrack_get_timestamp},
+ {"native_getMetrics", "()Landroid/os/PersistableBundle;",
+ (void *)android_media_AudioTrack_native_getMetrics},
{"native_set_loop", "(III)I", (void *)android_media_AudioTrack_set_loop},
{"native_reload_static", "()I", (void *)android_media_AudioTrack_reload},
{"native_get_output_sample_rate",
diff --git a/media/jni/android_media_MediaMetricsJNI.cpp b/core/jni/android_media_MediaMetricsJNI.cpp
similarity index 100%
rename from media/jni/android_media_MediaMetricsJNI.cpp
rename to core/jni/android_media_MediaMetricsJNI.cpp
diff --git a/media/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h
similarity index 100%
rename from media/jni/android_media_MediaMetricsJNI.h
rename to core/jni/android_media_MediaMetricsJNI.h
diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp
index 58c05b4..f0e449d 100644
--- a/core/jni/android_text_MeasuredParagraph.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -85,12 +85,14 @@
// Regular JNI
static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr,
- jcharArray javaText, jboolean computeHyphenation) {
+ jcharArray javaText, jboolean computeHyphenation,
+ jboolean computeLayout) {
ScopedCharArrayRO text(env, javaText);
const minikin::U16StringPiece textBuffer(text.get(), text.size());
// Pass the ownership to Java.
- return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation).release());
+ return toJLong(toBuilder(builderPtr)->build(textBuffer, computeHyphenation,
+ computeLayout).release());
}
// Regular JNI
@@ -99,6 +101,16 @@
}
// CriticalNative
+static jfloat nGetWidth(jlong ptr, jint start, jint end) {
+ minikin::MeasuredText* mt = toMeasuredParagraph(ptr);
+ float r = 0.0f;
+ for (int i = start; i < end; ++i) {
+ r += mt->widths[i];
+ }
+ return r;
+}
+
+// CriticalNative
static jlong nGetReleaseFunc() {
return toJLong(&releaseMeasuredParagraph);
}
@@ -108,10 +120,11 @@
{"nInitBuilder", "()J", (void*) nInitBuilder},
{"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
{"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
- {"nBuildNativeMeasuredParagraph", "(J[CZ)J", (void*) nBuildNativeMeasuredParagraph},
+ {"nBuildNativeMeasuredParagraph", "(J[CZZ)J", (void*) nBuildNativeMeasuredParagraph},
{"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
// MeasuredParagraph native functions.
+ {"nGetWidth", "(JII)F", (void*) nGetWidth}, // Critical Natives
{"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives
};
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index c6828c4..403937b 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1,1846 +1,1437 @@
-/* //device/libs/android_runtime/android_util_AssetManager.cpp
-**
-** Copyright 2006, 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.
-*/
+/*
+ * Copyright 2006, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
#define LOG_TAG "asset"
-#include <android_runtime/android_util_AssetManager.h>
-
#include <inttypes.h>
#include <linux/capability.h>
#include <stdio.h>
-#include <sys/types.h>
-#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/system_properties.h>
+#include <sys/types.h>
+#include <sys/wait.h>
#include <private/android_filesystem_config.h> // for AID_SYSTEM
-#include "androidfw/Asset.h"
-#include "androidfw/AssetManager.h"
-#include "androidfw/AttributeResolution.h"
-#include "androidfw/ResourceTypes.h"
+#include "android-base/logging.h"
+#include "android-base/properties.h"
+#include "android-base/stringprintf.h"
+#include "android_runtime/android_util_AssetManager.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_util_Binder.h"
+#include "androidfw/Asset.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/AttributeResolution.h"
+#include "androidfw/MutexGuard.h"
+#include "androidfw/ResourceTypes.h"
#include "core_jni_helpers.h"
#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedStringChars.h>
-#include <nativehelper/ScopedUtfChars.h>
+#include "nativehelper/JNIHelp.h"
+#include "nativehelper/ScopedPrimitiveArray.h"
+#include "nativehelper/ScopedStringChars.h"
+#include "nativehelper/ScopedUtfChars.h"
#include "utils/Log.h"
-#include "utils/misc.h"
#include "utils/String8.h"
+#include "utils/misc.h"
extern "C" int capget(cap_user_header_t hdrp, cap_user_data_t datap);
extern "C" int capset(cap_user_header_t hdrp, const cap_user_data_t datap);
+using ::android::base::StringPrintf;
namespace android {
-static const bool kThrowOnBadId = false;
-
// ----------------------------------------------------------------------------
-static struct typedvalue_offsets_t
-{
- jfieldID mType;
- jfieldID mData;
- jfieldID mString;
- jfieldID mAssetCookie;
- jfieldID mResourceId;
- jfieldID mChangingConfigurations;
- jfieldID mDensity;
+static struct typedvalue_offsets_t {
+ jfieldID mType;
+ jfieldID mData;
+ jfieldID mString;
+ jfieldID mAssetCookie;
+ jfieldID mResourceId;
+ jfieldID mChangingConfigurations;
+ jfieldID mDensity;
} gTypedValueOffsets;
-static struct assetfiledescriptor_offsets_t
-{
- jfieldID mFd;
- jfieldID mStartOffset;
- jfieldID mLength;
+static struct assetfiledescriptor_offsets_t {
+ jfieldID mFd;
+ jfieldID mStartOffset;
+ jfieldID mLength;
} gAssetFileDescriptorOffsets;
-static struct assetmanager_offsets_t
-{
- jfieldID mObject;
+static struct assetmanager_offsets_t {
+ jfieldID mObject;
} gAssetManagerOffsets;
-static struct sparsearray_offsets_t
-{
- jclass classObject;
- jmethodID constructor;
- jmethodID put;
+static struct {
+ jfieldID native_ptr;
+} gApkAssetsFields;
+
+static struct sparsearray_offsets_t {
+ jclass classObject;
+ jmethodID constructor;
+ jmethodID put;
} gSparseArrayOffsets;
-static struct configuration_offsets_t
-{
- jclass classObject;
- jmethodID constructor;
- jfieldID mSmallestScreenWidthDpOffset;
- jfieldID mScreenWidthDpOffset;
- jfieldID mScreenHeightDpOffset;
+static struct configuration_offsets_t {
+ jclass classObject;
+ jmethodID constructor;
+ jfieldID mSmallestScreenWidthDpOffset;
+ jfieldID mScreenWidthDpOffset;
+ jfieldID mScreenHeightDpOffset;
} gConfigurationOffsets;
-jclass g_stringClass = NULL;
+jclass g_stringClass = nullptr;
// ----------------------------------------------------------------------------
-static jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
- const Res_value& value, uint32_t ref, ssize_t block,
- uint32_t typeSpecFlags, ResTable_config* config = NULL);
+// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0.
+constexpr inline static jint ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) {
+ return cookie != kInvalidCookie ? static_cast<jint>(cookie + 1) : -1;
+}
-jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
- const Res_value& value, uint32_t ref, ssize_t block,
- uint32_t typeSpecFlags, ResTable_config* config)
-{
- env->SetIntField(outValue, gTypedValueOffsets.mType, value.dataType);
- env->SetIntField(outValue, gTypedValueOffsets.mAssetCookie,
- static_cast<jint>(table->getTableCookie(block)));
- env->SetIntField(outValue, gTypedValueOffsets.mData, value.data);
- env->SetObjectField(outValue, gTypedValueOffsets.mString, NULL);
- env->SetIntField(outValue, gTypedValueOffsets.mResourceId, ref);
- env->SetIntField(outValue, gTypedValueOffsets.mChangingConfigurations,
- typeSpecFlags);
- if (config != NULL) {
- env->SetIntField(outValue, gTypedValueOffsets.mDensity, config->density);
- }
- return block;
+constexpr inline static ApkAssetsCookie JavaCookieToApkAssetsCookie(jint cookie) {
+ return cookie > 0 ? static_cast<ApkAssetsCookie>(cookie - 1) : kInvalidCookie;
}
// This is called by zygote (running as user root) as part of preloadResources.
-static void verifySystemIdmaps()
-{
- pid_t pid;
- char system_id[10];
+static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) {
+ switch (pid_t pid = fork()) {
+ case -1:
+ PLOG(ERROR) << "failed to fork for idmap";
+ break;
- snprintf(system_id, sizeof(system_id), "%d", AID_SYSTEM);
+ // child
+ case 0: {
+ struct __user_cap_header_struct capheader;
+ struct __user_cap_data_struct capdata;
- switch (pid = fork()) {
- case -1:
- ALOGE("failed to fork for idmap: %s", strerror(errno));
- break;
- case 0: // child
- {
- struct __user_cap_header_struct capheader;
- struct __user_cap_data_struct capdata;
+ memset(&capheader, 0, sizeof(capheader));
+ memset(&capdata, 0, sizeof(capdata));
- memset(&capheader, 0, sizeof(capheader));
- memset(&capdata, 0, sizeof(capdata));
+ capheader.version = _LINUX_CAPABILITY_VERSION;
+ capheader.pid = 0;
- capheader.version = _LINUX_CAPABILITY_VERSION;
- capheader.pid = 0;
+ if (capget(&capheader, &capdata) != 0) {
+ PLOG(ERROR) << "capget";
+ exit(1);
+ }
- if (capget(&capheader, &capdata) != 0) {
- ALOGE("capget: %s\n", strerror(errno));
- exit(1);
- }
+ capdata.effective = capdata.permitted;
+ if (capset(&capheader, &capdata) != 0) {
+ PLOG(ERROR) << "capset";
+ exit(1);
+ }
- capdata.effective = capdata.permitted;
- if (capset(&capheader, &capdata) != 0) {
- ALOGE("capset: %s\n", strerror(errno));
- exit(1);
- }
+ if (setgid(AID_SYSTEM) != 0) {
+ PLOG(ERROR) << "setgid";
+ exit(1);
+ }
- if (setgid(AID_SYSTEM) != 0) {
- ALOGE("setgid: %s\n", strerror(errno));
- exit(1);
- }
+ if (setuid(AID_SYSTEM) != 0) {
+ PLOG(ERROR) << "setuid";
+ exit(1);
+ }
- if (setuid(AID_SYSTEM) != 0) {
- ALOGE("setuid: %s\n", strerror(errno));
- exit(1);
- }
+ // Generic idmap parameters
+ const char* argv[8];
+ int argc = 0;
+ struct stat st;
- // Generic idmap parameters
- const char* argv[8];
- int argc = 0;
- struct stat st;
+ memset(argv, 0, sizeof(argv));
+ argv[argc++] = AssetManager::IDMAP_BIN;
+ argv[argc++] = "--scan";
+ argv[argc++] = AssetManager::TARGET_PACKAGE_NAME;
+ argv[argc++] = AssetManager::TARGET_APK_PATH;
+ argv[argc++] = AssetManager::IDMAP_DIR;
- memset(argv, NULL, sizeof(argv));
- argv[argc++] = AssetManager::IDMAP_BIN;
- argv[argc++] = "--scan";
- argv[argc++] = AssetManager::TARGET_PACKAGE_NAME;
- argv[argc++] = AssetManager::TARGET_APK_PATH;
- argv[argc++] = AssetManager::IDMAP_DIR;
+ // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
+ // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
+ std::string overlay_theme_path = base::GetProperty(AssetManager::OVERLAY_THEME_DIR_PROPERTY,
+ "");
+ if (!overlay_theme_path.empty()) {
+ overlay_theme_path = std::string(AssetManager::OVERLAY_DIR) + "/" + overlay_theme_path;
+ if (stat(overlay_theme_path.c_str(), &st) == 0) {
+ argv[argc++] = overlay_theme_path.c_str();
+ }
+ }
- // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
- // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
- char subdir[PROP_VALUE_MAX];
- int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir);
- if (len > 0) {
- String8 overlayPath = String8(AssetManager::OVERLAY_DIR) + "/" + subdir;
- if (stat(overlayPath.string(), &st) == 0) {
- argv[argc++] = overlayPath.string();
- }
- }
- if (stat(AssetManager::OVERLAY_DIR, &st) == 0) {
- argv[argc++] = AssetManager::OVERLAY_DIR;
- }
+ if (stat(AssetManager::OVERLAY_DIR, &st) == 0) {
+ argv[argc++] = AssetManager::OVERLAY_DIR;
+ }
- // Finally, invoke idmap (if any overlay directory exists)
- if (argc > 5) {
- execv(AssetManager::IDMAP_BIN, (char* const*)argv);
- ALOGE("failed to execv for idmap: %s", strerror(errno));
- exit(1); // should never get here
- } else {
- exit(0);
- }
- }
- break;
- default: // parent
- waitpid(pid, NULL, 0);
- break;
- }
+ // Finally, invoke idmap (if any overlay directory exists)
+ if (argc > 5) {
+ execv(AssetManager::IDMAP_BIN, (char* const*)argv);
+ PLOG(ERROR) << "failed to execv for idmap";
+ exit(1); // should never get here
+ } else {
+ exit(0);
+ }
+ } break;
+
+ // parent
+ default:
+ waitpid(pid, nullptr, 0);
+ break;
+ }
+}
+
+static jint CopyValue(JNIEnv* env, ApkAssetsCookie cookie, const Res_value& value, uint32_t ref,
+ uint32_t type_spec_flags, ResTable_config* config, jobject out_typed_value) {
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mType, value.dataType);
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mAssetCookie,
+ ApkAssetsCookieToJavaCookie(cookie));
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mData, value.data);
+ env->SetObjectField(out_typed_value, gTypedValueOffsets.mString, nullptr);
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mResourceId, ref);
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mChangingConfigurations, type_spec_flags);
+ if (config != nullptr) {
+ env->SetIntField(out_typed_value, gTypedValueOffsets.mDensity, config->density);
+ }
+ return static_cast<jint>(ApkAssetsCookieToJavaCookie(cookie));
}
// ----------------------------------------------------------------------------
-// this guy is exported to other jni routines
-AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject obj)
-{
- jlong amHandle = env->GetLongField(obj, gAssetManagerOffsets.mObject);
- AssetManager* am = reinterpret_cast<AssetManager*>(amHandle);
- if (am != NULL) {
- return am;
- }
- jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!");
- return NULL;
-}
-
-static jlong android_content_AssetManager_openAsset(JNIEnv* env, jobject clazz,
- jstring fileName, jint mode)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- ALOGV("openAsset in %p (Java object %p)\n", am, clazz);
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "Empty file name");
- return -1;
- }
-
- if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM
- && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
- return -1;
- }
-
- Asset* a = am->open(fileName8.c_str(), (Asset::AccessMode)mode);
-
- if (a == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return -1;
- }
-
- //printf("Created Asset Stream: %p\n", a);
-
- return reinterpret_cast<jlong>(a);
-}
-
-static jobject returnParcelFileDescriptor(JNIEnv* env, Asset* a, jlongArray outOffsets)
-{
- off64_t startOffset, length;
- int fd = a->openFileDescriptor(&startOffset, &length);
- delete a;
-
- if (fd < 0) {
- jniThrowException(env, "java/io/FileNotFoundException",
- "This file can not be opened as a file descriptor; it is probably compressed");
- return NULL;
- }
-
- jlong* offsets = (jlong*)env->GetPrimitiveArrayCritical(outOffsets, 0);
- if (offsets == NULL) {
- close(fd);
- return NULL;
- }
-
- offsets[0] = startOffset;
- offsets[1] = length;
-
- env->ReleasePrimitiveArrayCritical(outOffsets, offsets, 0);
-
- jobject fileDesc = jniCreateFileDescriptor(env, fd);
- if (fileDesc == NULL) {
- close(fd);
- return NULL;
- }
-
- return newParcelFileDescriptor(env, fileDesc);
-}
-
-static jobject android_content_AssetManager_openAssetFd(JNIEnv* env, jobject clazz,
- jstring fileName, jlongArray outOffsets)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ALOGV("openAssetFd in %p (Java object %p)\n", am, clazz);
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- return NULL;
- }
-
- Asset* a = am->open(fileName8.c_str(), Asset::ACCESS_RANDOM);
-
- if (a == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return NULL;
- }
-
- //printf("Created Asset Stream: %p\n", a);
-
- return returnParcelFileDescriptor(env, a, outOffsets);
-}
-
-static jlong android_content_AssetManager_openNonAssetNative(JNIEnv* env, jobject clazz,
- jint cookie,
- jstring fileName,
- jint mode)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- ALOGV("openNonAssetNative in %p (Java object %p)\n", am, clazz);
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- return -1;
- }
-
- if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM
- && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
- return -1;
- }
-
- Asset* a = cookie
- ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(),
- (Asset::AccessMode)mode)
- : am->openNonAsset(fileName8.c_str(), (Asset::AccessMode)mode);
-
- if (a == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return -1;
- }
-
- //printf("Created Asset Stream: %p\n", a);
-
- return reinterpret_cast<jlong>(a);
-}
-
-static jobject android_content_AssetManager_openNonAssetFdNative(JNIEnv* env, jobject clazz,
- jint cookie,
- jstring fileName,
- jlongArray outOffsets)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ALOGV("openNonAssetFd in %p (Java object %p)\n", am, clazz);
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- return NULL;
- }
-
- Asset* a = cookie
- ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(), Asset::ACCESS_RANDOM)
- : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_RANDOM);
-
- if (a == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return NULL;
- }
-
- //printf("Created Asset Stream: %p\n", a);
-
- return returnParcelFileDescriptor(env, a, outOffsets);
-}
-
-static jobjectArray android_content_AssetManager_list(JNIEnv* env, jobject clazz,
- jstring fileName)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- return NULL;
- }
-
- AssetDir* dir = am->openDir(fileName8.c_str());
-
- if (dir == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return NULL;
- }
-
- size_t N = dir->getFileCount();
-
- jobjectArray array = env->NewObjectArray(dir->getFileCount(),
- g_stringClass, NULL);
- if (array == NULL) {
- delete dir;
- return NULL;
- }
-
- for (size_t i=0; i<N; i++) {
- const String8& name = dir->getFileName(i);
- jstring str = env->NewStringUTF(name.string());
- if (str == NULL) {
- delete dir;
- return NULL;
- }
- env->SetObjectArrayElement(array, i, str);
- env->DeleteLocalRef(str);
- }
-
- delete dir;
-
- return array;
-}
-
-static void android_content_AssetManager_destroyAsset(JNIEnv* env, jobject clazz,
- jlong assetHandle)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- //printf("Destroying Asset Stream: %p\n", a);
-
- if (a == NULL) {
- jniThrowNullPointerException(env, "asset");
- return;
- }
-
- delete a;
-}
-
-static jint android_content_AssetManager_readAssetChar(JNIEnv* env, jobject clazz,
- jlong assetHandle)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- if (a == NULL) {
- jniThrowNullPointerException(env, "asset");
- return -1;
- }
-
- uint8_t b;
- ssize_t res = a->read(&b, 1);
- return res == 1 ? b : -1;
-}
-
-static jint android_content_AssetManager_readAsset(JNIEnv* env, jobject clazz,
- jlong assetHandle, jbyteArray bArray,
- jint off, jint len)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- if (a == NULL || bArray == NULL) {
- jniThrowNullPointerException(env, "asset");
- return -1;
- }
-
- if (len == 0) {
- return 0;
- }
-
- jsize bLen = env->GetArrayLength(bArray);
- if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
- jniThrowException(env, "java/lang/IndexOutOfBoundsException", "");
- return -1;
- }
-
- jbyte* b = env->GetByteArrayElements(bArray, NULL);
- ssize_t res = a->read(b+off, len);
- env->ReleaseByteArrayElements(bArray, b, 0);
-
- if (res > 0) return static_cast<jint>(res);
-
- if (res < 0) {
- jniThrowException(env, "java/io/IOException", "");
- }
- return -1;
-}
-
-static jlong android_content_AssetManager_seekAsset(JNIEnv* env, jobject clazz,
- jlong assetHandle,
- jlong offset, jint whence)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- if (a == NULL) {
- jniThrowNullPointerException(env, "asset");
- return -1;
- }
-
- return a->seek(
- offset, (whence > 0) ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR));
-}
-
-static jlong android_content_AssetManager_getAssetLength(JNIEnv* env, jobject clazz,
- jlong assetHandle)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- if (a == NULL) {
- jniThrowNullPointerException(env, "asset");
- return -1;
- }
-
- return a->getLength();
-}
-
-static jlong android_content_AssetManager_getAssetRemainingLength(JNIEnv* env, jobject clazz,
- jlong assetHandle)
-{
- Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
- if (a == NULL) {
- jniThrowNullPointerException(env, "asset");
- return -1;
- }
-
- return a->getRemainingLength();
-}
-
-static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
- jstring path, jboolean appAsLib)
-{
- ScopedUtfChars path8(env, path);
- if (path8.c_str() == NULL) {
- return 0;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- int32_t cookie;
- bool res = am->addAssetPath(String8(path8.c_str()), &cookie, appAsLib);
-
- return (res) ? static_cast<jint>(cookie) : 0;
-}
-
-static jint android_content_AssetManager_addOverlayPath(JNIEnv* env, jobject clazz,
- jstring idmapPath)
-{
- ScopedUtfChars idmapPath8(env, idmapPath);
- if (idmapPath8.c_str() == NULL) {
- return 0;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- int32_t cookie;
- bool res = am->addOverlayPath(String8(idmapPath8.c_str()), &cookie);
-
- return (res) ? (jint)cookie : 0;
-}
-
-static jint android_content_AssetManager_addAssetFd(JNIEnv* env, jobject clazz,
- jobject fileDescriptor, jstring debugPathName,
- jboolean appAsLib)
-{
- ScopedUtfChars debugPathName8(env, debugPathName);
-
- int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
- if (fd < 0) {
- jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
- return 0;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- int dupfd = ::dup(fd);
- if (dupfd < 0) {
- jniThrowIOException(env, errno);
- return 0;
- }
-
- int32_t cookie;
- bool res = am->addAssetFd(dupfd, String8(debugPathName8.c_str()), &cookie, appAsLib);
-
- return (res) ? static_cast<jint>(cookie) : 0;
-}
-
-static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject clazz)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return JNI_TRUE;
- }
- return am->isUpToDate() ? JNI_TRUE : JNI_FALSE;
-}
-
-static jobjectArray getLocales(JNIEnv* env, jobject clazz, bool includeSystemLocales)
-{
- Vector<String8> locales;
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- am->getLocales(&locales, includeSystemLocales);
-
- const int N = locales.size();
-
- jobjectArray result = env->NewObjectArray(N, g_stringClass, NULL);
- if (result == NULL) {
- return NULL;
- }
-
- for (int i=0; i<N; i++) {
- jstring str = env->NewStringUTF(locales[i].string());
- if (str == NULL) {
- return NULL;
- }
- env->SetObjectArrayElement(result, i, str);
- env->DeleteLocalRef(str);
- }
-
- return result;
-}
-
-static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz)
-{
- return getLocales(env, clazz, true /* include system locales */);
-}
-
-static jobjectArray android_content_AssetManager_getNonSystemLocales(JNIEnv* env, jobject clazz)
-{
- return getLocales(env, clazz, false /* don't include system locales */);
-}
-
-static jobject constructConfigurationObject(JNIEnv* env, const ResTable_config& config) {
- jobject result = env->NewObject(gConfigurationOffsets.classObject,
- gConfigurationOffsets.constructor);
- if (result == NULL) {
- return NULL;
- }
-
- env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset,
- config.smallestScreenWidthDp);
- env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp);
- env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp);
-
- return result;
-}
-
-static jobjectArray getSizeConfigurationsInternal(JNIEnv* env,
- const Vector<ResTable_config>& configs) {
- const int N = configs.size();
- jobjectArray result = env->NewObjectArray(N, gConfigurationOffsets.classObject, NULL);
- if (result == NULL) {
- return NULL;
- }
-
- for (int i=0; i<N; i++) {
- jobject config = constructConfigurationObject(env, configs[i]);
- if (config == NULL) {
- env->DeleteLocalRef(result);
- return NULL;
- }
-
- env->SetObjectArrayElement(result, i, config);
- env->DeleteLocalRef(config);
- }
-
- return result;
-}
-
-static jobjectArray android_content_AssetManager_getSizeConfigurations(JNIEnv* env, jobject clazz) {
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- const ResTable& res(am->getResources());
- Vector<ResTable_config> configs;
- res.getConfigurations(&configs, false /* ignoreMipmap */, true /* ignoreAndroidPackage */);
-
- return getSizeConfigurationsInternal(env, configs);
-}
-
-static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz,
- jint mcc, jint mnc,
- jstring locale, jint orientation,
- jint touchscreen, jint density,
- jint keyboard, jint keyboardHidden,
- jint navigation,
- jint screenWidth, jint screenHeight,
- jint smallestScreenWidthDp,
- jint screenWidthDp, jint screenHeightDp,
- jint screenLayout, jint uiMode,
- jint colorMode, jint sdkVersion)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return;
- }
-
- ResTable_config config;
- memset(&config, 0, sizeof(config));
-
- const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;
-
- // Constants duplicated from Java class android.content.res.Configuration.
- static const jint kScreenLayoutRoundMask = 0x300;
- static const jint kScreenLayoutRoundShift = 8;
-
- config.mcc = (uint16_t)mcc;
- config.mnc = (uint16_t)mnc;
- config.orientation = (uint8_t)orientation;
- config.touchscreen = (uint8_t)touchscreen;
- config.density = (uint16_t)density;
- config.keyboard = (uint8_t)keyboard;
- config.inputFlags = (uint8_t)keyboardHidden;
- config.navigation = (uint8_t)navigation;
- config.screenWidth = (uint16_t)screenWidth;
- config.screenHeight = (uint16_t)screenHeight;
- config.smallestScreenWidthDp = (uint16_t)smallestScreenWidthDp;
- config.screenWidthDp = (uint16_t)screenWidthDp;
- config.screenHeightDp = (uint16_t)screenHeightDp;
- config.screenLayout = (uint8_t)screenLayout;
- config.uiMode = (uint8_t)uiMode;
- config.colorMode = (uint8_t)colorMode;
- config.sdkVersion = (uint16_t)sdkVersion;
- config.minorVersion = 0;
-
- // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
- // in C++. We must extract the round qualifier out of the Java screenLayout and put it
- // into screenLayout2.
- config.screenLayout2 =
- (uint8_t)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
-
- am->setConfiguration(config, locale8);
-
- if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8);
-}
-
-static jint android_content_AssetManager_getResourceIdentifier(JNIEnv* env, jobject clazz,
- jstring name,
- jstring defType,
- jstring defPackage)
-{
- ScopedStringChars name16(env, name);
- if (name16.get() == NULL) {
- return 0;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- const char16_t* defType16 = reinterpret_cast<const char16_t*>(defType)
- ? reinterpret_cast<const char16_t*>(env->GetStringChars(defType, NULL))
- : NULL;
- jsize defTypeLen = defType
- ? env->GetStringLength(defType) : 0;
- const char16_t* defPackage16 = reinterpret_cast<const char16_t*>(defPackage)
- ? reinterpret_cast<const char16_t*>(env->GetStringChars(defPackage,
- NULL))
- : NULL;
- jsize defPackageLen = defPackage
- ? env->GetStringLength(defPackage) : 0;
-
- jint ident = am->getResources().identifierForName(
- reinterpret_cast<const char16_t*>(name16.get()), name16.size(),
- defType16, defTypeLen, defPackage16, defPackageLen);
-
- if (defPackage16) {
- env->ReleaseStringChars(defPackage,
- reinterpret_cast<const jchar*>(defPackage16));
- }
- if (defType16) {
- env->ReleaseStringChars(defType,
- reinterpret_cast<const jchar*>(defType16));
- }
-
- return ident;
-}
-
-static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject clazz,
- jint resid)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, true, &name)) {
- return NULL;
- }
-
- String16 str;
- if (name.package != NULL) {
- str.setTo(name.package, name.packageLen);
- }
- if (name.type8 != NULL || name.type != NULL) {
- if (str.size() > 0) {
- char16_t div = ':';
- str.append(&div, 1);
- }
- if (name.type8 != NULL) {
- str.append(String16(name.type8, name.typeLen));
- } else {
- str.append(name.type, name.typeLen);
- }
- }
- if (name.name8 != NULL || name.name != NULL) {
- if (str.size() > 0) {
- char16_t div = '/';
- str.append(&div, 1);
- }
- if (name.name8 != NULL) {
- str.append(String16(name.name8, name.nameLen));
- } else {
- str.append(name.name, name.nameLen);
- }
- }
-
- return env->NewString((const jchar*)str.string(), str.size());
-}
-
-static jstring android_content_AssetManager_getResourcePackageName(JNIEnv* env, jobject clazz,
- jint resid)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, true, &name)) {
- return NULL;
- }
-
- if (name.package != NULL) {
- return env->NewString((const jchar*)name.package, name.packageLen);
- }
-
- return NULL;
-}
-
-static jstring android_content_AssetManager_getResourceTypeName(JNIEnv* env, jobject clazz,
- jint resid)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, true, &name)) {
- return NULL;
- }
-
- if (name.type8 != NULL) {
- return env->NewStringUTF(name.type8);
- }
-
- if (name.type != NULL) {
- return env->NewString((const jchar*)name.type, name.typeLen);
- }
-
- return NULL;
-}
-
-static jstring android_content_AssetManager_getResourceEntryName(JNIEnv* env, jobject clazz,
- jint resid)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
-
- ResTable::resource_name name;
- if (!am->getResources().getResourceName(resid, true, &name)) {
- return NULL;
- }
-
- if (name.name8 != NULL) {
- return env->NewStringUTF(name.name8);
- }
-
- if (name.name != NULL) {
- return env->NewString((const jchar*)name.name, name.nameLen);
- }
-
- return NULL;
-}
-
-static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
- jint ident,
- jshort density,
- jobject outValue,
- jboolean resolve)
-{
- if (outValue == NULL) {
- jniThrowNullPointerException(env, "outValue");
- return 0;
- }
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- const ResTable& res(am->getResources());
-
- Res_value value;
- ResTable_config config;
- uint32_t typeSpecFlags;
- ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return 0;
- }
- }
- uint32_t ref = ident;
- if (resolve) {
- block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return 0;
- }
- }
- }
- if (block >= 0) {
- return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);
- }
-
- return static_cast<jint>(block);
-}
-
-static jint android_content_AssetManager_loadResourceBagValue(JNIEnv* env, jobject clazz,
- jint ident, jint bagEntryId,
- jobject outValue, jboolean resolve)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- const ResTable& res(am->getResources());
-
- // Now lock down the resource object and start pulling stuff from it.
- res.lock();
-
- ssize_t block = -1;
- Res_value value;
-
- const ResTable::bag_entry* entry = NULL;
- uint32_t typeSpecFlags;
- ssize_t entryCount = res.getBagLocked(ident, &entry, &typeSpecFlags);
-
- for (ssize_t i=0; i<entryCount; i++) {
- if (((uint32_t)bagEntryId) == entry->map.name.ident) {
- block = entry->stringBlock;
- value = entry->map.value;
- }
- entry++;
- }
-
- res.unlock();
-
- if (block < 0) {
- return static_cast<jint>(block);
- }
-
- uint32_t ref = ident;
- if (resolve) {
- block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return 0;
- }
- }
- }
- if (block >= 0) {
- return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags);
- }
-
- return static_cast<jint>(block);
-}
-
-static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- return am->getResources().getTableCount();
-}
-
-static jlong android_content_AssetManager_getNativeStringBlock(JNIEnv* env, jobject clazz,
- jint block)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- return reinterpret_cast<jlong>(am->getResources().getTableStringBlock(block));
-}
-
-static jstring android_content_AssetManager_getCookieName(JNIEnv* env, jobject clazz,
- jint cookie)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
- String8 name(am->getAssetPath(static_cast<int32_t>(cookie)));
- if (name.length() == 0) {
- jniThrowException(env, "java/lang/IndexOutOfBoundsException", "Empty cookie name");
- return NULL;
- }
- jstring str = env->NewStringUTF(name.string());
- return str;
-}
-
-static jobject android_content_AssetManager_getAssignedPackageIdentifiers(JNIEnv* env, jobject clazz)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- const ResTable& res = am->getResources();
-
- jobject sparseArray = env->NewObject(gSparseArrayOffsets.classObject,
- gSparseArrayOffsets.constructor);
- const size_t N = res.getBasePackageCount();
- for (size_t i = 0; i < N; i++) {
- const String16 name = res.getBasePackageName(i);
- env->CallVoidMethod(
- sparseArray, gSparseArrayOffsets.put,
- static_cast<jint>(res.getBasePackageId(i)),
- env->NewString(reinterpret_cast<const jchar*>(name.string()),
- name.size()));
- }
- return sparseArray;
-}
-
-static jlong android_content_AssetManager_newTheme(JNIEnv* env, jobject clazz)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- return reinterpret_cast<jlong>(new ResTable::Theme(am->getResources()));
-}
-
-static void android_content_AssetManager_deleteTheme(JNIEnv* env, jobject clazz,
- jlong themeHandle)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- delete theme;
-}
-
-static void android_content_AssetManager_applyThemeStyle(JNIEnv* env, jobject clazz,
- jlong themeHandle,
- jint styleRes,
- jboolean force)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- theme->applyStyle(styleRes, force ? true : false);
-}
-
-static void android_content_AssetManager_copyTheme(JNIEnv* env, jobject clazz,
- jlong destHandle, jlong srcHandle)
-{
- ResTable::Theme* dest = reinterpret_cast<ResTable::Theme*>(destHandle);
- ResTable::Theme* src = reinterpret_cast<ResTable::Theme*>(srcHandle);
- dest->setTo(*src);
-}
-
-static void android_content_AssetManager_clearTheme(JNIEnv* env, jobject clazz, jlong themeHandle)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- theme->clear();
-}
-
-static jint android_content_AssetManager_loadThemeAttributeValue(
- JNIEnv* env, jobject clazz, jlong themeHandle, jint ident, jobject outValue, jboolean resolve)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- const ResTable& res(theme->getResTable());
-
- Res_value value;
- // XXX value could be different in different configs!
- uint32_t typeSpecFlags = 0;
- ssize_t block = theme->getAttribute(ident, &value, &typeSpecFlags);
- uint32_t ref = 0;
- if (resolve) {
- block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return 0;
- }
- }
- }
- return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block;
-}
-
-static jint android_content_AssetManager_getThemeChangingConfigurations(JNIEnv* env, jobject clazz,
- jlong themeHandle)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- return theme->getChangingConfigurations();
-}
-
-static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz,
- jlong themeHandle, jint pri,
- jstring tag, jstring prefix)
-{
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
- const ResTable& res(theme->getResTable());
- (void)res;
-
- // XXX Need to use params.
- theme->dumpToLog();
-}
-
-static jboolean android_content_AssetManager_resolveAttrs(JNIEnv* env, jobject clazz,
- jlong themeToken,
- jint defStyleAttr,
- jint defStyleRes,
- jintArray inValues,
- jintArray attrs,
- jintArray outValues,
- jintArray outIndices)
-{
- if (themeToken == 0) {
- jniThrowNullPointerException(env, "theme token");
- return JNI_FALSE;
- }
- if (attrs == NULL) {
- jniThrowNullPointerException(env, "attrs");
- return JNI_FALSE;
- }
- if (outValues == NULL) {
- jniThrowNullPointerException(env, "out values");
- return JNI_FALSE;
- }
-
- const jsize NI = env->GetArrayLength(attrs);
- const jsize NV = env->GetArrayLength(outValues);
- if (NV < (NI*STYLE_NUM_ENTRIES)) {
- jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small");
- return JNI_FALSE;
- }
-
- jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
- if (src == NULL) {
- return JNI_FALSE;
- }
-
- jint* srcValues = (jint*)env->GetPrimitiveArrayCritical(inValues, 0);
- const jsize NSV = srcValues == NULL ? 0 : env->GetArrayLength(inValues);
-
- jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
- if (baseDest == NULL) {
- env->ReleasePrimitiveArrayCritical(attrs, src, 0);
- return JNI_FALSE;
- }
-
- jint* indices = NULL;
- if (outIndices != NULL) {
- if (env->GetArrayLength(outIndices) > NI) {
- indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0);
- }
- }
-
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
- bool result = ResolveAttrs(theme, defStyleAttr, defStyleRes,
- (uint32_t*) srcValues, NSV,
- (uint32_t*) src, NI,
- (uint32_t*) baseDest,
- (uint32_t*) indices);
-
- if (indices != NULL) {
- env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
- }
- env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
- env->ReleasePrimitiveArrayCritical(inValues, srcValues, 0);
- env->ReleasePrimitiveArrayCritical(attrs, src, 0);
- return result ? JNI_TRUE : JNI_FALSE;
-}
-
-static void android_content_AssetManager_applyStyle(JNIEnv* env, jobject, jlong themeToken,
- jint defStyleAttr, jint defStyleRes, jlong xmlParserToken, jintArray attrsObj, jint length,
- jlong outValuesAddress, jlong outIndicesAddress) {
- jint* attrs = env->GetIntArrayElements(attrsObj, 0);
- ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
- ResXMLParser* xmlParser = reinterpret_cast<ResXMLParser*>(xmlParserToken);
- uint32_t* outValues = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outValuesAddress));
- uint32_t* outIndices = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outIndicesAddress));
- ApplyStyle(theme, xmlParser, defStyleAttr, defStyleRes,
- reinterpret_cast<const uint32_t*>(attrs), length, outValues, outIndices);
- env->ReleaseIntArrayElements(attrsObj, attrs, JNI_ABORT);
-}
-
-static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, jobject clazz,
- jlong xmlParserToken,
- jintArray attrs,
- jintArray outValues,
- jintArray outIndices)
-{
- if (xmlParserToken == 0) {
- jniThrowNullPointerException(env, "xmlParserToken");
- return JNI_FALSE;
- }
- if (attrs == NULL) {
- jniThrowNullPointerException(env, "attrs");
- return JNI_FALSE;
- }
- if (outValues == NULL) {
- jniThrowNullPointerException(env, "out values");
- return JNI_FALSE;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return JNI_FALSE;
- }
- const ResTable& res(am->getResources());
- ResXMLParser* xmlParser = (ResXMLParser*)xmlParserToken;
-
- const jsize NI = env->GetArrayLength(attrs);
- const jsize NV = env->GetArrayLength(outValues);
- if (NV < (NI*STYLE_NUM_ENTRIES)) {
- jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small");
- return JNI_FALSE;
- }
-
- jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
- if (src == NULL) {
- return JNI_FALSE;
- }
-
- jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
- if (baseDest == NULL) {
- env->ReleasePrimitiveArrayCritical(attrs, src, 0);
- return JNI_FALSE;
- }
-
- jint* indices = NULL;
- if (outIndices != NULL) {
- if (env->GetArrayLength(outIndices) > NI) {
- indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0);
- }
- }
-
- bool result = RetrieveAttributes(&res, xmlParser,
- (uint32_t*) src, NI,
- (uint32_t*) baseDest,
- (uint32_t*) indices);
-
- if (indices != NULL) {
- env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
- }
- env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
- env->ReleasePrimitiveArrayCritical(attrs, src, 0);
- return result ? JNI_TRUE : JNI_FALSE;
-}
-
-static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz,
- jint id)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
- const ResTable& res(am->getResources());
-
- res.lock();
- const ResTable::bag_entry* defStyleEnt = NULL;
- ssize_t bagOff = res.getBagLocked(id, &defStyleEnt);
- res.unlock();
-
- return static_cast<jint>(bagOff);
-}
-
-static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject clazz,
- jint id,
- jintArray outValues)
-{
- if (outValues == NULL) {
- jniThrowNullPointerException(env, "out values");
- return JNI_FALSE;
- }
-
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return JNI_FALSE;
- }
- const ResTable& res(am->getResources());
- ResTable_config config;
- Res_value value;
- ssize_t block;
-
- const jsize NV = env->GetArrayLength(outValues);
-
- jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
- jint* dest = baseDest;
- if (dest == NULL) {
- jniThrowException(env, "java/lang/OutOfMemoryError", "");
- return JNI_FALSE;
- }
-
- // Now lock down the resource object and start pulling stuff from it.
- res.lock();
-
- const ResTable::bag_entry* arrayEnt = NULL;
- uint32_t arrayTypeSetFlags = 0;
- ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags);
- const ResTable::bag_entry* endArrayEnt = arrayEnt +
- (bagOff >= 0 ? bagOff : 0);
-
- int i = 0;
- uint32_t typeSetFlags;
- while (i < NV && arrayEnt < endArrayEnt) {
- block = arrayEnt->stringBlock;
- typeSetFlags = arrayTypeSetFlags;
- config.density = 0;
- value = arrayEnt->map.value;
-
- uint32_t resid = 0;
- if (value.dataType != Res_value::TYPE_NULL) {
- // Take care of resolving the found resource to its final value.
- //printf("Resolving attribute reference\n");
- ssize_t newBlock = res.resolveReference(&value, block, &resid,
- &typeSetFlags, &config);
- if (kThrowOnBadId) {
- if (newBlock == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return JNI_FALSE;
- }
- }
- if (newBlock >= 0) block = newBlock;
- }
-
- // Deal with the special @null value -- it turns back to TYPE_NULL.
- if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
- value.dataType = Res_value::TYPE_NULL;
- value.data = Res_value::DATA_NULL_UNDEFINED;
- }
-
- //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
-
- // Write the final value back to Java.
- dest[STYLE_TYPE] = value.dataType;
- dest[STYLE_DATA] = value.data;
- dest[STYLE_ASSET_COOKIE] = reinterpret_cast<jint>(res.getTableCookie(block));
- dest[STYLE_RESOURCE_ID] = resid;
- dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
- dest[STYLE_DENSITY] = config.density;
- dest += STYLE_NUM_ENTRIES;
- i+= STYLE_NUM_ENTRIES;
- arrayEnt++;
- }
-
- i /= STYLE_NUM_ENTRIES;
-
- res.unlock();
-
- env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
-
- return i;
-}
-
-static jlong android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz,
- jint cookie,
- jstring fileName)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return 0;
- }
-
- ALOGV("openXmlAsset in %p (Java object %p)\n", am, clazz);
-
- ScopedUtfChars fileName8(env, fileName);
- if (fileName8.c_str() == NULL) {
- return 0;
- }
-
- int32_t assetCookie = static_cast<int32_t>(cookie);
- Asset* a = assetCookie
- ? am->openNonAsset(assetCookie, fileName8.c_str(), Asset::ACCESS_BUFFER)
- : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER, &assetCookie);
-
- if (a == NULL) {
- jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
- return 0;
- }
-
- const DynamicRefTable* dynamicRefTable =
- am->getResources().getDynamicRefTableForCookie(assetCookie);
- ResXMLTree* block = new ResXMLTree(dynamicRefTable);
- status_t err = block->setTo(a->getBuffer(true), a->getLength(), true);
- a->close();
- delete a;
-
- if (err != NO_ERROR) {
- jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
- return 0;
- }
-
- return reinterpret_cast<jlong>(block);
-}
-
-static jintArray android_content_AssetManager_getArrayStringInfo(JNIEnv* env, jobject clazz,
- jint arrayResId)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
- const ResTable& res(am->getResources());
-
- const ResTable::bag_entry* startOfBag;
- const ssize_t N = res.lockBag(arrayResId, &startOfBag);
- if (N < 0) {
- return NULL;
- }
-
- jintArray array = env->NewIntArray(N * 2);
- if (array == NULL) {
- res.unlockBag(startOfBag);
- return NULL;
- }
-
- Res_value value;
- const ResTable::bag_entry* bag = startOfBag;
- for (size_t i = 0, j = 0; ((ssize_t)i)<N; i++, bag++) {
- jint stringIndex = -1;
- jint stringBlock = 0;
- value = bag->map.value;
-
- // Take care of resolving the found resource to its final value.
- stringBlock = res.resolveReference(&value, bag->stringBlock, NULL);
- if (value.dataType == Res_value::TYPE_STRING) {
- stringIndex = value.data;
- }
-
- if (kThrowOnBadId) {
- if (stringBlock == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return array;
- }
- }
-
- //todo: It might be faster to allocate a C array to contain
- // the blocknums and indices, put them in there and then
- // do just one SetIntArrayRegion()
- env->SetIntArrayRegion(array, j, 1, &stringBlock);
- env->SetIntArrayRegion(array, j + 1, 1, &stringIndex);
- j = j + 2;
- }
- res.unlockBag(startOfBag);
- return array;
-}
-
-static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz,
- jint arrayResId)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
- const ResTable& res(am->getResources());
-
- const ResTable::bag_entry* startOfBag;
- const ssize_t N = res.lockBag(arrayResId, &startOfBag);
- if (N < 0) {
- return NULL;
- }
-
- jobjectArray array = env->NewObjectArray(N, g_stringClass, NULL);
- if (env->ExceptionCheck()) {
- res.unlockBag(startOfBag);
- return NULL;
- }
-
- Res_value value;
- const ResTable::bag_entry* bag = startOfBag;
- size_t strLen = 0;
- for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
- value = bag->map.value;
- jstring str = NULL;
-
- // Take care of resolving the found resource to its final value.
- ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return array;
- }
- }
- if (value.dataType == Res_value::TYPE_STRING) {
- const ResStringPool* pool = res.getTableStringBlock(block);
- const char* str8 = pool->string8At(value.data, &strLen);
- if (str8 != NULL) {
- str = env->NewStringUTF(str8);
- } else {
- const char16_t* str16 = pool->stringAt(value.data, &strLen);
- str = env->NewString(reinterpret_cast<const jchar*>(str16),
- strLen);
- }
-
- // If one of our NewString{UTF} calls failed due to memory, an
- // exception will be pending.
- if (env->ExceptionCheck()) {
- res.unlockBag(startOfBag);
- return NULL;
- }
-
- env->SetObjectArrayElement(array, i, str);
-
- // str is not NULL at that point, otherwise ExceptionCheck would have been true.
- // If we have a large amount of strings in our array, we might
- // overflow the local reference table of the VM.
- env->DeleteLocalRef(str);
- }
- }
- res.unlockBag(startOfBag);
- return array;
-}
-
-static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, jobject clazz,
- jint arrayResId)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
- const ResTable& res(am->getResources());
-
- const ResTable::bag_entry* startOfBag;
- const ssize_t N = res.lockBag(arrayResId, &startOfBag);
- if (N < 0) {
- return NULL;
- }
-
- jintArray array = env->NewIntArray(N);
- if (array == NULL) {
- res.unlockBag(startOfBag);
- return NULL;
- }
-
- Res_value value;
- const ResTable::bag_entry* bag = startOfBag;
- for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
- value = bag->map.value;
-
- // Take care of resolving the found resource to its final value.
- ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
- if (kThrowOnBadId) {
- if (block == BAD_INDEX) {
- jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
- return array;
- }
- }
- if (value.dataType >= Res_value::TYPE_FIRST_INT
- && value.dataType <= Res_value::TYPE_LAST_INT) {
- int intVal = value.data;
- env->SetIntArrayRegion(array, i, 1, &intVal);
- }
- }
- res.unlockBag(startOfBag);
- return array;
-}
-
-static jintArray android_content_AssetManager_getStyleAttributes(JNIEnv* env, jobject clazz,
- jint styleId)
-{
- AssetManager* am = assetManagerForJavaObject(env, clazz);
- if (am == NULL) {
- return NULL;
- }
- const ResTable& res(am->getResources());
-
- const ResTable::bag_entry* startOfBag;
- const ssize_t N = res.lockBag(styleId, &startOfBag);
- if (N < 0) {
- return NULL;
- }
-
- jintArray array = env->NewIntArray(N);
- if (array == NULL) {
- res.unlockBag(startOfBag);
- return NULL;
- }
-
- const ResTable::bag_entry* bag = startOfBag;
- for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
- int resourceId = bag->map.name.ident;
- env->SetIntArrayRegion(array, i, 1, &resourceId);
- }
- res.unlockBag(startOfBag);
- return array;
-}
-
-static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
-{
- if (isSystem) {
- verifySystemIdmaps();
- }
- AssetManager* am = new AssetManager();
- if (am == NULL) {
- jniThrowException(env, "java/lang/OutOfMemoryError", "");
- return;
- }
-
- am->addDefaultAssets();
-
- ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
- env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
-}
-
-static void android_content_AssetManager_destroy(JNIEnv* env, jobject clazz)
-{
- AssetManager* am = (AssetManager*)
- (env->GetLongField(clazz, gAssetManagerOffsets.mObject));
- ALOGV("Destroying AssetManager %p for Java object %p\n", am, clazz);
- if (am != NULL) {
- delete am;
- env->SetLongField(clazz, gAssetManagerOffsets.mObject, 0);
- }
-}
-
-static jint android_content_AssetManager_getGlobalAssetCount(JNIEnv* env, jobject clazz)
-{
- return Asset::getGlobalCount();
-}
-
-static jobject android_content_AssetManager_getAssetAllocations(JNIEnv* env, jobject clazz)
-{
- String8 alloc = Asset::getAssetAllocations();
- if (alloc.length() <= 0) {
- return NULL;
- }
-
- jstring str = env->NewStringUTF(alloc.string());
- return str;
-}
-
-static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env, jobject clazz)
-{
- return AssetManager::getGlobalCount();
-}
-
-// ----------------------------------------------------------------------------
-
-/*
- * JNI registration.
- */
-static const JNINativeMethod gAssetManagerMethods[] = {
- /* name, signature, funcPtr */
-
- // Basic asset stuff.
- { "openAsset", "(Ljava/lang/String;I)J",
- (void*) android_content_AssetManager_openAsset },
- { "openAssetFd", "(Ljava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
- (void*) android_content_AssetManager_openAssetFd },
- { "openNonAssetNative", "(ILjava/lang/String;I)J",
- (void*) android_content_AssetManager_openNonAssetNative },
- { "openNonAssetFdNative", "(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
- (void*) android_content_AssetManager_openNonAssetFdNative },
- { "list", "(Ljava/lang/String;)[Ljava/lang/String;",
- (void*) android_content_AssetManager_list },
- { "destroyAsset", "(J)V",
- (void*) android_content_AssetManager_destroyAsset },
- { "readAssetChar", "(J)I",
- (void*) android_content_AssetManager_readAssetChar },
- { "readAsset", "(J[BII)I",
- (void*) android_content_AssetManager_readAsset },
- { "seekAsset", "(JJI)J",
- (void*) android_content_AssetManager_seekAsset },
- { "getAssetLength", "(J)J",
- (void*) android_content_AssetManager_getAssetLength },
- { "getAssetRemainingLength", "(J)J",
- (void*) android_content_AssetManager_getAssetRemainingLength },
- { "addAssetPathNative", "(Ljava/lang/String;Z)I",
- (void*) android_content_AssetManager_addAssetPath },
- { "addAssetFdNative", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)I",
- (void*) android_content_AssetManager_addAssetFd },
- { "addOverlayPathNative", "(Ljava/lang/String;)I",
- (void*) android_content_AssetManager_addOverlayPath },
- { "isUpToDate", "()Z",
- (void*) android_content_AssetManager_isUpToDate },
-
- // Resources.
- { "getLocales", "()[Ljava/lang/String;",
- (void*) android_content_AssetManager_getLocales },
- { "getNonSystemLocales", "()[Ljava/lang/String;",
- (void*) android_content_AssetManager_getNonSystemLocales },
- { "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
- (void*) android_content_AssetManager_getSizeConfigurations },
- { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIIII)V",
- (void*) android_content_AssetManager_setConfiguration },
- { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
- (void*) android_content_AssetManager_getResourceIdentifier },
- { "getResourceName","(I)Ljava/lang/String;",
- (void*) android_content_AssetManager_getResourceName },
- { "getResourcePackageName","(I)Ljava/lang/String;",
- (void*) android_content_AssetManager_getResourcePackageName },
- { "getResourceTypeName","(I)Ljava/lang/String;",
- (void*) android_content_AssetManager_getResourceTypeName },
- { "getResourceEntryName","(I)Ljava/lang/String;",
- (void*) android_content_AssetManager_getResourceEntryName },
- { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I",
- (void*) android_content_AssetManager_loadResourceValue },
- { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I",
- (void*) android_content_AssetManager_loadResourceBagValue },
- { "getStringBlockCount","()I",
- (void*) android_content_AssetManager_getStringBlockCount },
- { "getNativeStringBlock","(I)J",
- (void*) android_content_AssetManager_getNativeStringBlock },
- { "getCookieName","(I)Ljava/lang/String;",
- (void*) android_content_AssetManager_getCookieName },
- { "getAssignedPackageIdentifiers","()Landroid/util/SparseArray;",
- (void*) android_content_AssetManager_getAssignedPackageIdentifiers },
-
- // Themes.
- { "newTheme", "()J",
- (void*) android_content_AssetManager_newTheme },
- { "deleteTheme", "(J)V",
- (void*) android_content_AssetManager_deleteTheme },
- { "applyThemeStyle", "(JIZ)V",
- (void*) android_content_AssetManager_applyThemeStyle },
- { "copyTheme", "(JJ)V",
- (void*) android_content_AssetManager_copyTheme },
- { "clearTheme", "(J)V",
- (void*) android_content_AssetManager_clearTheme },
- { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I",
- (void*) android_content_AssetManager_loadThemeAttributeValue },
- { "getThemeChangingConfigurations", "(J)I",
- (void*) android_content_AssetManager_getThemeChangingConfigurations },
- { "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V",
- (void*) android_content_AssetManager_dumpTheme },
- { "applyStyle","(JIIJ[IIJJ)V",
- (void*) android_content_AssetManager_applyStyle },
- { "resolveAttrs","(JII[I[I[I[I)Z",
- (void*) android_content_AssetManager_resolveAttrs },
- { "retrieveAttributes","(J[I[I[I)Z",
- (void*) android_content_AssetManager_retrieveAttributes },
- { "getArraySize","(I)I",
- (void*) android_content_AssetManager_getArraySize },
- { "retrieveArray","(I[I)I",
- (void*) android_content_AssetManager_retrieveArray },
-
- // XML files.
- { "openXmlAssetNative", "(ILjava/lang/String;)J",
- (void*) android_content_AssetManager_openXmlAssetNative },
-
- // Arrays.
- { "getArrayStringResource","(I)[Ljava/lang/String;",
- (void*) android_content_AssetManager_getArrayStringResource },
- { "getArrayStringInfo","(I)[I",
- (void*) android_content_AssetManager_getArrayStringInfo },
- { "getArrayIntResource","(I)[I",
- (void*) android_content_AssetManager_getArrayIntResource },
- { "getStyleAttributes","(I)[I",
- (void*) android_content_AssetManager_getStyleAttributes },
-
- // Bookkeeping.
- { "init", "(Z)V",
- (void*) android_content_AssetManager_init },
- { "destroy", "()V",
- (void*) android_content_AssetManager_destroy },
- { "getGlobalAssetCount", "()I",
- (void*) android_content_AssetManager_getGlobalAssetCount },
- { "getAssetAllocations", "()Ljava/lang/String;",
- (void*) android_content_AssetManager_getAssetAllocations },
- { "getGlobalAssetManagerCount", "()I",
- (void*) android_content_AssetManager_getGlobalAssetManagerCount },
+// Let the opaque type AAssetManager refer to a guarded AssetManager2 instance.
+struct GuardedAssetManager : public ::AAssetManager {
+ Guarded<AssetManager2> guarded_assetmanager;
};
-int register_android_content_AssetManager(JNIEnv* env)
-{
- jclass typedValue = FindClassOrDie(env, "android/util/TypedValue");
- gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I");
- gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I");
- gTypedValueOffsets.mString = GetFieldIDOrDie(env, typedValue, "string",
- "Ljava/lang/CharSequence;");
- gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I");
- gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I");
- gTypedValueOffsets.mChangingConfigurations = GetFieldIDOrDie(env, typedValue,
- "changingConfigurations", "I");
- gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I");
+::AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) {
+ jlong assetmanager_handle = env->GetLongField(jassetmanager, gAssetManagerOffsets.mObject);
+ ::AAssetManager* am = reinterpret_cast<::AAssetManager*>(assetmanager_handle);
+ if (am == nullptr) {
+ jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!");
+ return nullptr;
+ }
+ return am;
+}
- jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor");
- gAssetFileDescriptorOffsets.mFd = GetFieldIDOrDie(env, assetFd, "mFd",
- "Landroid/os/ParcelFileDescriptor;");
- gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J");
- gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J");
+Guarded<AssetManager2>* AssetManagerForNdkAssetManager(::AAssetManager* assetmanager) {
+ if (assetmanager == nullptr) {
+ return nullptr;
+ }
+ return &reinterpret_cast<GuardedAssetManager*>(assetmanager)->guarded_assetmanager;
+}
- jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager");
- gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J");
+Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) {
+ return AssetManagerForNdkAssetManager(NdkAssetManagerForJavaObject(env, jassetmanager));
+}
- jclass stringClass = FindClassOrDie(env, "java/lang/String");
- g_stringClass = MakeGlobalRefOrDie(env, stringClass);
+static Guarded<AssetManager2>& AssetManagerFromLong(jlong ptr) {
+ return *AssetManagerForNdkAssetManager(reinterpret_cast<AAssetManager*>(ptr));
+}
- jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray");
- gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass);
- gSparseArrayOffsets.constructor = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject,
- "<init>", "()V");
- gSparseArrayOffsets.put = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put",
- "(ILjava/lang/Object;)V");
+static jobject ReturnParcelFileDescriptor(JNIEnv* env, std::unique_ptr<Asset> asset,
+ jlongArray out_offsets) {
+ off64_t start_offset, length;
+ int fd = asset->openFileDescriptor(&start_offset, &length);
+ asset.reset();
- jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration");
- gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass);
- gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass,
- "<init>", "()V");
- gConfigurationOffsets.mSmallestScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass,
- "smallestScreenWidthDp", "I");
- gConfigurationOffsets.mScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass,
- "screenWidthDp", "I");
- gConfigurationOffsets.mScreenHeightDpOffset = GetFieldIDOrDie(env, configurationClass,
- "screenHeightDp", "I");
+ if (fd < 0) {
+ jniThrowException(env, "java/io/FileNotFoundException",
+ "This file can not be opened as a file descriptor; it is probably "
+ "compressed");
+ return nullptr;
+ }
- return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods,
- NELEM(gAssetManagerMethods));
+ jlong* offsets = reinterpret_cast<jlong*>(env->GetPrimitiveArrayCritical(out_offsets, 0));
+ if (offsets == nullptr) {
+ close(fd);
+ return nullptr;
+ }
+
+ offsets[0] = start_offset;
+ offsets[1] = length;
+
+ env->ReleasePrimitiveArrayCritical(out_offsets, offsets, 0);
+
+ jobject file_desc = jniCreateFileDescriptor(env, fd);
+ if (file_desc == nullptr) {
+ close(fd);
+ return nullptr;
+ }
+ return newParcelFileDescriptor(env, file_desc);
+}
+
+static jint NativeGetGlobalAssetCount(JNIEnv* /*env*/, jobject /*clazz*/) {
+ return Asset::getGlobalCount();
+}
+
+static jobject NativeGetAssetAllocations(JNIEnv* env, jobject /*clazz*/) {
+ String8 alloc = Asset::getAssetAllocations();
+ if (alloc.length() <= 0) {
+ return nullptr;
+ }
+ return env->NewStringUTF(alloc.string());
+}
+
+static jint NativeGetGlobalAssetManagerCount(JNIEnv* /*env*/, jobject /*clazz*/) {
+ // TODO(adamlesinski): Switch to AssetManager2.
+ return AssetManager::getGlobalCount();
+}
+
+static jlong NativeCreate(JNIEnv* /*env*/, jclass /*clazz*/) {
+ // AssetManager2 needs to be protected by a lock. To avoid cache misses, we allocate the lock and
+ // AssetManager2 in a contiguous block (GuardedAssetManager).
+ return reinterpret_cast<jlong>(new GuardedAssetManager());
+}
+
+static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+ delete reinterpret_cast<GuardedAssetManager*>(ptr);
+}
+
+static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+ jobjectArray apk_assets_array, jboolean invalidate_caches) {
+ const jsize apk_assets_len = env->GetArrayLength(apk_assets_array);
+ std::vector<const ApkAssets*> apk_assets;
+ apk_assets.reserve(apk_assets_len);
+ for (jsize i = 0; i < apk_assets_len; i++) {
+ jobject obj = env->GetObjectArrayElement(apk_assets_array, i);
+ if (obj == nullptr) {
+ std::string msg = StringPrintf("ApkAssets at index %d is null", i);
+ jniThrowNullPointerException(env, msg.c_str());
+ return;
+ }
+
+ jlong apk_assets_native_ptr = env->GetLongField(obj, gApkAssetsFields.native_ptr);
+ if (env->ExceptionCheck()) {
+ return;
+ }
+ apk_assets.push_back(reinterpret_cast<const ApkAssets*>(apk_assets_native_ptr));
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ assetmanager->SetApkAssets(apk_assets, invalidate_caches);
+}
+
+static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc,
+ jstring locale, jint orientation, jint touchscreen, jint density,
+ jint keyboard, jint keyboard_hidden, jint navigation,
+ jint screen_width, jint screen_height,
+ jint smallest_screen_width_dp, jint screen_width_dp,
+ jint screen_height_dp, jint screen_layout, jint ui_mode,
+ jint color_mode, jint major_version) {
+ ResTable_config configuration;
+ memset(&configuration, 0, sizeof(configuration));
+ configuration.mcc = static_cast<uint16_t>(mcc);
+ configuration.mnc = static_cast<uint16_t>(mnc);
+ configuration.orientation = static_cast<uint8_t>(orientation);
+ configuration.touchscreen = static_cast<uint8_t>(touchscreen);
+ configuration.density = static_cast<uint16_t>(density);
+ configuration.keyboard = static_cast<uint8_t>(keyboard);
+ configuration.inputFlags = static_cast<uint8_t>(keyboard_hidden);
+ configuration.navigation = static_cast<uint8_t>(navigation);
+ configuration.screenWidth = static_cast<uint16_t>(screen_width);
+ configuration.screenHeight = static_cast<uint16_t>(screen_height);
+ configuration.smallestScreenWidthDp = static_cast<uint16_t>(smallest_screen_width_dp);
+ configuration.screenWidthDp = static_cast<uint16_t>(screen_width_dp);
+ configuration.screenHeightDp = static_cast<uint16_t>(screen_height_dp);
+ configuration.screenLayout = static_cast<uint8_t>(screen_layout);
+ configuration.uiMode = static_cast<uint8_t>(ui_mode);
+ configuration.colorMode = static_cast<uint8_t>(color_mode);
+ configuration.sdkVersion = static_cast<uint16_t>(major_version);
+
+ if (locale != nullptr) {
+ ScopedUtfChars locale_utf8(env, locale);
+ CHECK(locale_utf8.c_str() != nullptr);
+ configuration.setBcp47Locale(locale_utf8.c_str());
+ }
+
+ // Constants duplicated from Java class android.content.res.Configuration.
+ static const jint kScreenLayoutRoundMask = 0x300;
+ static const jint kScreenLayoutRoundShift = 8;
+
+ // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
+ // in C++. We must extract the round qualifier out of the Java screenLayout and put it
+ // into screenLayout2.
+ configuration.screenLayout2 =
+ static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ assetmanager->SetConfiguration(configuration);
+}
+
+static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+
+ jobject sparse_array =
+ env->NewObject(gSparseArrayOffsets.classObject, gSparseArrayOffsets.constructor);
+
+ if (sparse_array == nullptr) {
+ // An exception is pending.
+ return nullptr;
+ }
+
+ assetmanager->ForEachPackage([&](const std::string& package_name, uint8_t package_id) {
+ jstring jpackage_name = env->NewStringUTF(package_name.c_str());
+ if (jpackage_name == nullptr) {
+ // An exception is pending.
+ return;
+ }
+
+ env->CallVoidMethod(sparse_array, gSparseArrayOffsets.put, static_cast<jint>(package_id),
+ jpackage_name);
+ });
+ return sparse_array;
+}
+
+static jobjectArray NativeList(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring path) {
+ ScopedUtfChars path_utf8(env, path);
+ if (path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return nullptr;
+ }
+
+ std::vector<std::string> all_file_paths;
+ {
+ StringPiece normalized_path = path_utf8.c_str();
+ if (normalized_path.data()[0] == '/') {
+ normalized_path = normalized_path.substr(1);
+ }
+ std::string root_path = StringPrintf("assets/%s", normalized_path.data());
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ for (const ApkAssets* assets : assetmanager->GetApkAssets()) {
+ assets->ForEachFile(root_path, [&](const StringPiece& file_path, FileType type) {
+ if (type == FileType::kFileTypeRegular) {
+ all_file_paths.push_back(file_path.to_string());
+ }
+ });
+ }
+ }
+
+ jobjectArray array = env->NewObjectArray(all_file_paths.size(), g_stringClass, nullptr);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ jsize index = 0;
+ for (const std::string& file_path : all_file_paths) {
+ jstring java_string = env->NewStringUTF(file_path.c_str());
+
+ // Check for errors creating the strings (if malformed or no memory).
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ env->SetObjectArrayElement(array, index++, java_string);
+
+ // If we have a large amount of string in our array, we might overflow the
+ // local reference table of the VM.
+ env->DeleteLocalRef(java_string);
+ }
+ return array;
+}
+
+static jlong NativeOpenAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path,
+ jint access_mode) {
+ ScopedUtfChars asset_path_utf8(env, asset_path);
+ if (asset_path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return 0;
+ }
+
+ if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM &&
+ access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
+ return 0;
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::unique_ptr<Asset> asset =
+ assetmanager->Open(asset_path_utf8.c_str(), static_cast<Asset::AccessMode>(access_mode));
+ if (!asset) {
+ jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(asset.release());
+}
+
+static jobject NativeOpenAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path,
+ jlongArray out_offsets) {
+ ScopedUtfChars asset_path_utf8(env, asset_path);
+ if (asset_path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return nullptr;
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::unique_ptr<Asset> asset = assetmanager->Open(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM);
+ if (!asset) {
+ jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+ return nullptr;
+ }
+ return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets);
+}
+
+static jlong NativeOpenNonAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie,
+ jstring asset_path, jint access_mode) {
+ ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+ ScopedUtfChars asset_path_utf8(env, asset_path);
+ if (asset_path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return 0;
+ }
+
+ if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM &&
+ access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
+ return 0;
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::unique_ptr<Asset> asset;
+ if (cookie != kInvalidCookie) {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie,
+ static_cast<Asset::AccessMode>(access_mode));
+ } else {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(),
+ static_cast<Asset::AccessMode>(access_mode));
+ }
+
+ if (!asset) {
+ jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(asset.release());
+}
+
+static jobject NativeOpenNonAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie,
+ jstring asset_path, jlongArray out_offsets) {
+ ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+ ScopedUtfChars asset_path_utf8(env, asset_path);
+ if (asset_path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return nullptr;
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::unique_ptr<Asset> asset;
+ if (cookie != kInvalidCookie) {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM);
+ } else {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM);
+ }
+
+ if (!asset) {
+ jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+ return nullptr;
+ }
+ return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets);
+}
+
+static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint jcookie,
+ jstring asset_path) {
+ ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+ ScopedUtfChars asset_path_utf8(env, asset_path);
+ if (asset_path_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return 0;
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::unique_ptr<Asset> asset;
+ if (cookie != kInvalidCookie) {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM);
+ } else {
+ asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM, &cookie);
+ }
+
+ if (!asset) {
+ jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+ return 0;
+ }
+
+ // May be nullptr.
+ const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie);
+
+ std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table);
+ status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
+ asset.reset();
+
+ if (err != NO_ERROR) {
+ jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+ return 0;
+ }
+ return reinterpret_cast<jlong>(xml_tree.release());
+}
+
+static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
+ jshort density, jobject typed_value,
+ jboolean resolve_references) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Res_value value;
+ ResTable_config selected_config;
+ uint32_t flags;
+ ApkAssetsCookie cookie =
+ assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
+ static_cast<uint16_t>(density), &value, &selected_config, &flags);
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+
+ uint32_t ref = static_cast<uint32_t>(resid);
+ if (resolve_references) {
+ cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+ }
+ return CopyValue(env, cookie, value, ref, flags, &selected_config, typed_value);
+}
+
+static jint NativeGetResourceBagValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
+ jint bag_entry_id, jobject typed_value) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+
+ uint32_t type_spec_flags = bag->type_spec_flags;
+ ApkAssetsCookie cookie = kInvalidCookie;
+ const Res_value* bag_value = nullptr;
+ for (const ResolvedBag::Entry& entry : bag) {
+ if (entry.key == static_cast<uint32_t>(bag_entry_id)) {
+ cookie = entry.cookie;
+ bag_value = &entry.value;
+
+ // Keep searching (the old implementation did that).
+ }
+ }
+
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+
+ Res_value value = *bag_value;
+ uint32_t ref = static_cast<uint32_t>(resid);
+ ResTable_config selected_config;
+ cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &type_spec_flags, &ref);
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+ return CopyValue(env, cookie, value, ref, type_spec_flags, nullptr, typed_value);
+}
+
+static jintArray NativeGetStyleAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return nullptr;
+ }
+
+ jintArray array = env->NewIntArray(bag->entry_count);
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ for (uint32_t i = 0; i < bag->entry_count; i++) {
+ jint attr_resid = bag->entries[i].key;
+ env->SetIntArrayRegion(array, i, 1, &attr_resid);
+ }
+ return array;
+}
+
+static jobjectArray NativeGetResourceStringArray(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+ jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return nullptr;
+ }
+
+ jobjectArray array = env->NewObjectArray(bag->entry_count, g_stringClass, nullptr);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ for (uint32_t i = 0; i < bag->entry_count; i++) {
+ const ResolvedBag::Entry& entry = bag->entries[i];
+
+ // Resolve any references to their final value.
+ Res_value value = entry.value;
+ ResTable_config selected_config;
+ uint32_t flags;
+ uint32_t ref;
+ ApkAssetsCookie cookie =
+ assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ return nullptr;
+ }
+
+ if (value.dataType == Res_value::TYPE_STRING) {
+ const ApkAssets* apk_assets = assetmanager->GetApkAssets()[cookie];
+ const ResStringPool* pool = apk_assets->GetLoadedArsc()->GetStringPool();
+
+ jstring java_string = nullptr;
+ size_t str_len;
+ const char* str_utf8 = pool->string8At(value.data, &str_len);
+ if (str_utf8 != nullptr) {
+ java_string = env->NewStringUTF(str_utf8);
+ } else {
+ const char16_t* str_utf16 = pool->stringAt(value.data, &str_len);
+ java_string = env->NewString(reinterpret_cast<const jchar*>(str_utf16), str_len);
+ }
+
+ // Check for errors creating the strings (if malformed or no memory).
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ env->SetObjectArrayElement(array, i, java_string);
+
+ // If we have a large amount of string in our array, we might overflow the
+ // local reference table of the VM.
+ env->DeleteLocalRef(java_string);
+ }
+ }
+ return array;
+}
+
+static jintArray NativeGetResourceStringArrayInfo(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+ jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return nullptr;
+ }
+
+ jintArray array = env->NewIntArray(bag->entry_count * 2);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr));
+ if (buffer == nullptr) {
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < bag->entry_count; i++) {
+ const ResolvedBag::Entry& entry = bag->entries[i];
+ Res_value value = entry.value;
+ ResTable_config selected_config;
+ uint32_t flags;
+ uint32_t ref;
+ ApkAssetsCookie cookie =
+ assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT);
+ return nullptr;
+ }
+
+ jint string_index = -1;
+ if (value.dataType == Res_value::TYPE_STRING) {
+ string_index = static_cast<jint>(value.data);
+ }
+
+ buffer[i * 2] = ApkAssetsCookieToJavaCookie(cookie);
+ buffer[(i * 2) + 1] = string_index;
+ }
+ env->ReleasePrimitiveArrayCritical(array, buffer, 0);
+ return array;
+}
+
+static jintArray NativeGetResourceIntArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return nullptr;
+ }
+
+ jintArray array = env->NewIntArray(bag->entry_count);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr));
+ if (buffer == nullptr) {
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < bag->entry_count; i++) {
+ const ResolvedBag::Entry& entry = bag->entries[i];
+ Res_value value = entry.value;
+ ResTable_config selected_config;
+ uint32_t flags;
+ uint32_t ref;
+ ApkAssetsCookie cookie =
+ assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT);
+ return nullptr;
+ }
+
+ if (value.dataType >= Res_value::TYPE_FIRST_INT && value.dataType <= Res_value::TYPE_LAST_INT) {
+ buffer[i] = static_cast<jint>(value.data);
+ }
+ }
+ env->ReleasePrimitiveArrayCritical(array, buffer, 0);
+ return array;
+}
+
+static jint NativeGetResourceArraySize(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return -1;
+ }
+ return static_cast<jint>(bag->entry_count);
+}
+
+static jint NativeGetResourceArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
+ jintArray out_data) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+ if (bag == nullptr) {
+ return -1;
+ }
+
+ const jsize out_data_length = env->GetArrayLength(out_data);
+ if (env->ExceptionCheck()) {
+ return -1;
+ }
+
+ if (static_cast<jsize>(bag->entry_count) > out_data_length * STYLE_NUM_ENTRIES) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Input array is not large enough");
+ return -1;
+ }
+
+ jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_data, nullptr));
+ if (buffer == nullptr) {
+ return -1;
+ }
+
+ jint* cursor = buffer;
+ for (size_t i = 0; i < bag->entry_count; i++) {
+ const ResolvedBag::Entry& entry = bag->entries[i];
+ Res_value value = entry.value;
+ ResTable_config selected_config;
+ selected_config.density = 0;
+ uint32_t flags = bag->type_spec_flags;
+ uint32_t ref;
+ ApkAssetsCookie cookie =
+ assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ env->ReleasePrimitiveArrayCritical(out_data, buffer, JNI_ABORT);
+ return -1;
+ }
+
+ // Deal with the special @null value -- it turns back to TYPE_NULL.
+ if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
+ value.dataType = Res_value::TYPE_NULL;
+ value.data = Res_value::DATA_NULL_UNDEFINED;
+ }
+
+ cursor[STYLE_TYPE] = static_cast<jint>(value.dataType);
+ cursor[STYLE_DATA] = static_cast<jint>(value.data);
+ cursor[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
+ cursor[STYLE_RESOURCE_ID] = static_cast<jint>(ref);
+ cursor[STYLE_CHANGING_CONFIGURATIONS] = static_cast<jint>(flags);
+ cursor[STYLE_DENSITY] = static_cast<jint>(selected_config.density);
+ cursor += STYLE_NUM_ENTRIES;
+ }
+ env->ReleasePrimitiveArrayCritical(out_data, buffer, 0);
+ return static_cast<jint>(bag->entry_count);
+}
+
+static jint NativeGetResourceIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring name,
+ jstring def_type, jstring def_package) {
+ ScopedUtfChars name_utf8(env, name);
+ if (name_utf8.c_str() == nullptr) {
+ // This will throw NPE.
+ return 0;
+ }
+
+ std::string type;
+ if (def_type != nullptr) {
+ ScopedUtfChars type_utf8(env, def_type);
+ CHECK(type_utf8.c_str() != nullptr);
+ type = type_utf8.c_str();
+ }
+
+ std::string package;
+ if (def_package != nullptr) {
+ ScopedUtfChars package_utf8(env, def_package);
+ CHECK(package_utf8.c_str() != nullptr);
+ package = package_utf8.c_str();
+ }
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ return static_cast<jint>(assetmanager->GetResourceId(name_utf8.c_str(), type, package));
+}
+
+static jstring NativeGetResourceName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ AssetManager2::ResourceName name;
+ if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+ return nullptr;
+ }
+
+ std::string result;
+ if (name.package != nullptr) {
+ result.append(name.package, name.package_len);
+ }
+
+ if (name.type != nullptr || name.type16 != nullptr) {
+ if (!result.empty()) {
+ result += ":";
+ }
+
+ if (name.type != nullptr) {
+ result.append(name.type, name.type_len);
+ } else {
+ result += util::Utf16ToUtf8(StringPiece16(name.type16, name.type_len));
+ }
+ }
+
+ if (name.entry != nullptr || name.entry16 != nullptr) {
+ if (!result.empty()) {
+ result += "/";
+ }
+
+ if (name.entry != nullptr) {
+ result.append(name.entry, name.entry_len);
+ } else {
+ result += util::Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len));
+ }
+ }
+ return env->NewStringUTF(result.c_str());
+}
+
+static jstring NativeGetResourcePackageName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ AssetManager2::ResourceName name;
+ if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+ return nullptr;
+ }
+
+ if (name.package != nullptr) {
+ return env->NewStringUTF(name.package);
+ }
+ return nullptr;
+}
+
+static jstring NativeGetResourceTypeName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ AssetManager2::ResourceName name;
+ if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+ return nullptr;
+ }
+
+ if (name.type != nullptr) {
+ return env->NewStringUTF(name.type);
+ } else if (name.type16 != nullptr) {
+ return env->NewString(reinterpret_cast<const jchar*>(name.type16), name.type_len);
+ }
+ return nullptr;
+}
+
+static jstring NativeGetResourceEntryName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ AssetManager2::ResourceName name;
+ if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+ return nullptr;
+ }
+
+ if (name.entry != nullptr) {
+ return env->NewStringUTF(name.entry);
+ } else if (name.entry16 != nullptr) {
+ return env->NewString(reinterpret_cast<const jchar*>(name.entry16), name.entry_len);
+ }
+ return nullptr;
+}
+
+static jobjectArray NativeGetLocales(JNIEnv* env, jclass /*class*/, jlong ptr,
+ jboolean exclude_system) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::set<std::string> locales =
+ assetmanager->GetResourceLocales(exclude_system, true /*merge_equivalent_languages*/);
+
+ jobjectArray array = env->NewObjectArray(locales.size(), g_stringClass, nullptr);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ size_t idx = 0;
+ for (const std::string& locale : locales) {
+ jstring java_string = env->NewStringUTF(locale.c_str());
+ if (java_string == nullptr) {
+ return nullptr;
+ }
+ env->SetObjectArrayElement(array, idx++, java_string);
+ env->DeleteLocalRef(java_string);
+ }
+ return array;
+}
+
+static jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config& config) {
+ jobject result =
+ env->NewObject(gConfigurationOffsets.classObject, gConfigurationOffsets.constructor);
+ if (result == nullptr) {
+ return nullptr;
+ }
+
+ env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset,
+ config.smallestScreenWidthDp);
+ env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp);
+ env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp);
+ return result;
+}
+
+static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ std::set<ResTable_config> configurations =
+ assetmanager->GetResourceConfigurations(true /*exclude_system*/, false /*exclude_mipmap*/);
+
+ jobjectArray array =
+ env->NewObjectArray(configurations.size(), gConfigurationOffsets.classObject, nullptr);
+ if (array == nullptr) {
+ return nullptr;
+ }
+
+ size_t idx = 0;
+ for (const ResTable_config& configuration : configurations) {
+ jobject java_configuration = ConstructConfigurationObject(env, configuration);
+ if (java_configuration == nullptr) {
+ return nullptr;
+ }
+
+ env->SetObjectArrayElement(array, idx++, java_configuration);
+ env->DeleteLocalRef(java_configuration);
+ }
+ return array;
+}
+
+static void NativeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+ jint def_style_attr, jint def_style_resid, jlong xml_parser_ptr,
+ jintArray java_attrs, jlong out_values_ptr, jlong out_indices_ptr) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ CHECK(theme->GetAssetManager() == &(*assetmanager));
+ (void) assetmanager;
+
+ ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
+ uint32_t* out_values = reinterpret_cast<uint32_t*>(out_values_ptr);
+ uint32_t* out_indices = reinterpret_cast<uint32_t*>(out_indices_ptr);
+
+ jsize attrs_len = env->GetArrayLength(java_attrs);
+ jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
+ if (attrs == nullptr) {
+ return;
+ }
+
+ ApplyStyle(theme, xml_parser, static_cast<uint32_t>(def_style_attr),
+ static_cast<uint32_t>(def_style_resid), reinterpret_cast<uint32_t*>(attrs), attrs_len,
+ out_values, out_indices);
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+}
+
+static jboolean NativeResolveAttrs(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+ jint def_style_attr, jint def_style_resid, jintArray java_values,
+ jintArray java_attrs, jintArray out_java_values,
+ jintArray out_java_indices) {
+ const jsize attrs_len = env->GetArrayLength(java_attrs);
+ const jsize out_values_len = env->GetArrayLength(out_java_values);
+ if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) {
+ jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small");
+ return JNI_FALSE;
+ }
+
+ jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
+ if (attrs == nullptr) {
+ return JNI_FALSE;
+ }
+
+ jint* values = nullptr;
+ jsize values_len = 0;
+ if (java_values != nullptr) {
+ values_len = env->GetArrayLength(java_values);
+ values = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_values, nullptr));
+ if (values == nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ return JNI_FALSE;
+ }
+ }
+
+ jint* out_values =
+ reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr));
+ if (out_values == nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ if (values != nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT);
+ }
+ return JNI_FALSE;
+ }
+
+ jint* out_indices = nullptr;
+ if (out_java_indices != nullptr) {
+ jsize out_indices_len = env->GetArrayLength(out_java_indices);
+ if (out_indices_len > attrs_len) {
+ out_indices =
+ reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr));
+ if (out_indices == nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ if (values != nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT);
+ }
+ env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT);
+ return JNI_FALSE;
+ }
+ }
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ CHECK(theme->GetAssetManager() == &(*assetmanager));
+ (void) assetmanager;
+
+ bool result = ResolveAttrs(
+ theme, static_cast<uint32_t>(def_style_attr), static_cast<uint32_t>(def_style_resid),
+ reinterpret_cast<uint32_t*>(values), values_len, reinterpret_cast<uint32_t*>(attrs),
+ attrs_len, reinterpret_cast<uint32_t*>(out_values), reinterpret_cast<uint32_t*>(out_indices));
+ if (out_indices != nullptr) {
+ env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0);
+ }
+
+ env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0);
+ if (values != nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT);
+ }
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean NativeRetrieveAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+ jlong xml_parser_ptr, jintArray java_attrs,
+ jintArray out_java_values, jintArray out_java_indices) {
+ const jsize attrs_len = env->GetArrayLength(java_attrs);
+ const jsize out_values_len = env->GetArrayLength(out_java_values);
+ if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) {
+ jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small");
+ return JNI_FALSE;
+ }
+
+ jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
+ if (attrs == nullptr) {
+ return JNI_FALSE;
+ }
+
+ jint* out_values =
+ reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr));
+ if (out_values == nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ return JNI_FALSE;
+ }
+
+ jint* out_indices = nullptr;
+ if (out_java_indices != nullptr) {
+ jsize out_indices_len = env->GetArrayLength(out_java_indices);
+ if (out_indices_len > attrs_len) {
+ out_indices =
+ reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr));
+ if (out_indices == nullptr) {
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT);
+ return JNI_FALSE;
+ }
+ }
+ }
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
+
+ bool result = RetrieveAttributes(assetmanager.get(), xml_parser,
+ reinterpret_cast<uint32_t*>(attrs), attrs_len,
+ reinterpret_cast<uint32_t*>(out_values),
+ reinterpret_cast<uint32_t*>(out_indices));
+
+ if (out_indices != nullptr) {
+ env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0);
+ }
+ env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0);
+ env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+ return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jlong NativeThemeCreate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ return reinterpret_cast<jlong>(assetmanager->NewTheme().release());
+}
+
+static void NativeThemeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) {
+ delete reinterpret_cast<Theme*>(theme_ptr);
+}
+
+static void NativeThemeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+ jint resid, jboolean force) {
+ // AssetManager is accessed via the theme, so grab an explicit lock here.
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ CHECK(theme->GetAssetManager() == &(*assetmanager));
+ (void) assetmanager;
+ theme->ApplyStyle(static_cast<uint32_t>(resid), force);
+
+ // TODO(adamlesinski): Consider surfacing exception when result is failure.
+ // CTS currently expects no exceptions from this method.
+ // std::string error_msg = StringPrintf("Failed to apply style 0x%08x to theme", resid);
+ // jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
+}
+
+static void NativeThemeCopy(JNIEnv* env, jclass /*clazz*/, jlong dst_theme_ptr,
+ jlong src_theme_ptr) {
+ Theme* dst_theme = reinterpret_cast<Theme*>(dst_theme_ptr);
+ Theme* src_theme = reinterpret_cast<Theme*>(src_theme_ptr);
+ if (!dst_theme->SetTo(*src_theme)) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Themes are from different AssetManagers");
+ }
+}
+
+static void NativeThemeClear(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) {
+ reinterpret_cast<Theme*>(theme_ptr)->Clear();
+}
+
+static jint NativeThemeGetAttributeValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+ jint resid, jobject typed_value,
+ jboolean resolve_references) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ CHECK(theme->GetAssetManager() == &(*assetmanager));
+ (void) assetmanager;
+
+ Res_value value;
+ uint32_t flags;
+ ApkAssetsCookie cookie = theme->GetAttribute(static_cast<uint32_t>(resid), &value, &flags);
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+
+ uint32_t ref = 0u;
+ if (resolve_references) {
+ ResTable_config selected_config;
+ cookie =
+ theme->GetAssetManager()->ResolveReference(cookie, &value, &selected_config, &flags, &ref);
+ if (cookie == kInvalidCookie) {
+ return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+ }
+ }
+ return CopyValue(env, cookie, value, ref, flags, nullptr, typed_value);
+}
+
+static void NativeThemeDump(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+ jint priority, jstring tag, jstring prefix) {
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ CHECK(theme->GetAssetManager() == &(*assetmanager));
+ (void) assetmanager;
+ (void) theme;
+ (void) priority;
+ (void) tag;
+ (void) prefix;
+}
+
+static jint NativeThemeGetChangingConfigurations(JNIEnv* /*env*/, jclass /*clazz*/,
+ jlong theme_ptr) {
+ Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+ return static_cast<jint>(theme->GetChangingConfigurations());
+}
+
+static void NativeAssetDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+ delete reinterpret_cast<Asset*>(asset_ptr);
+}
+
+static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+ Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+ uint8_t b;
+ ssize_t res = asset->read(&b, sizeof(b));
+ return res == sizeof(b) ? static_cast<jint>(b) : -1;
+}
+
+static jint NativeAssetRead(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jbyteArray java_buffer,
+ jint offset, jint len) {
+ if (len == 0) {
+ return 0;
+ }
+
+ jsize buffer_len = env->GetArrayLength(java_buffer);
+ if (offset < 0 || offset >= buffer_len || len < 0 || len > buffer_len ||
+ offset > buffer_len - len) {
+ jniThrowException(env, "java/lang/IndexOutOfBoundsException", "");
+ return -1;
+ }
+
+ ScopedByteArrayRW byte_array(env, java_buffer);
+ if (byte_array.get() == nullptr) {
+ return -1;
+ }
+
+ Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+ ssize_t res = asset->read(byte_array.get() + offset, len);
+ if (res < 0) {
+ jniThrowException(env, "java/io/IOException", "");
+ return -1;
+ }
+ return res > 0 ? static_cast<jint>(res) : -1;
+}
+
+static jlong NativeAssetSeek(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jlong offset,
+ jint whence) {
+ Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+ return static_cast<jlong>(asset->seek(
+ static_cast<off64_t>(offset), (whence > 0 ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR))));
+}
+
+static jlong NativeAssetGetLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+ Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+ return static_cast<jlong>(asset->getLength());
+}
+
+static jlong NativeAssetGetRemainingLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+ Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+ return static_cast<jlong>(asset->getRemainingLength());
+}
+
+// ----------------------------------------------------------------------------
+
+// JNI registration.
+static const JNINativeMethod gAssetManagerMethods[] = {
+ // AssetManager setup methods.
+ {"nativeCreate", "()J", (void*)NativeCreate},
+ {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+ {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
+ {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIII)V",
+ (void*)NativeSetConfiguration},
+ {"nativeGetAssignedPackageIdentifiers", "(J)Landroid/util/SparseArray;",
+ (void*)NativeGetAssignedPackageIdentifiers},
+
+ // AssetManager file methods.
+ {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
+ {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
+ {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+ (void*)NativeOpenAssetFd},
+ {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
+ {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+ (void*)NativeOpenNonAssetFd},
+ {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+
+ // AssetManager resource methods.
+ {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
+ {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
+ (void*)NativeGetResourceBagValue},
+ {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
+ {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
+ (void*)NativeGetResourceStringArray},
+ {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
+ {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
+ {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
+ {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
+
+ // AssetManager resource name/ID methods.
+ {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+ (void*)NativeGetResourceIdentifier},
+ {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
+ {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName},
+ {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
+ {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
+ {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
+ {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
+ (void*)NativeGetSizeConfigurations},
+
+ // Style attribute related methods.
+ {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
+ {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
+ {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
+
+ // Theme related methods.
+ {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
+ {"nativeThemeDestroy", "(J)V", (void*)NativeThemeDestroy},
+ {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
+ {"nativeThemeCopy", "(JJ)V", (void*)NativeThemeCopy},
+ {"nativeThemeClear", "(J)V", (void*)NativeThemeClear},
+ {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
+ (void*)NativeThemeGetAttributeValue},
+ {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
+ {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations},
+
+ // AssetInputStream methods.
+ {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
+ {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
+ {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
+ {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
+ {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
+ {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
+
+ // System/idmap related methods.
+ {"nativeVerifySystemIdmaps", "()V", (void*)NativeVerifySystemIdmaps},
+
+ // Global management/debug methods.
+ {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
+ {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
+ {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
+};
+
+int register_android_content_AssetManager(JNIEnv* env) {
+ jclass apk_assets_class = FindClassOrDie(env, "android/content/res/ApkAssets");
+ gApkAssetsFields.native_ptr = GetFieldIDOrDie(env, apk_assets_class, "mNativePtr", "J");
+
+ jclass typedValue = FindClassOrDie(env, "android/util/TypedValue");
+ gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I");
+ gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I");
+ gTypedValueOffsets.mString =
+ GetFieldIDOrDie(env, typedValue, "string", "Ljava/lang/CharSequence;");
+ gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I");
+ gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I");
+ gTypedValueOffsets.mChangingConfigurations =
+ GetFieldIDOrDie(env, typedValue, "changingConfigurations", "I");
+ gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I");
+
+ jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor");
+ gAssetFileDescriptorOffsets.mFd =
+ GetFieldIDOrDie(env, assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;");
+ gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J");
+ gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J");
+
+ jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager");
+ gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J");
+
+ jclass stringClass = FindClassOrDie(env, "java/lang/String");
+ g_stringClass = MakeGlobalRefOrDie(env, stringClass);
+
+ jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray");
+ gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass);
+ gSparseArrayOffsets.constructor =
+ GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "<init>", "()V");
+ gSparseArrayOffsets.put =
+ GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put", "(ILjava/lang/Object;)V");
+
+ jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration");
+ gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass);
+ gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass, "<init>", "()V");
+ gConfigurationOffsets.mSmallestScreenWidthDpOffset =
+ GetFieldIDOrDie(env, configurationClass, "smallestScreenWidthDp", "I");
+ gConfigurationOffsets.mScreenWidthDpOffset =
+ GetFieldIDOrDie(env, configurationClass, "screenWidthDp", "I");
+ gConfigurationOffsets.mScreenHeightDpOffset =
+ GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I");
+
+ return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods,
+ NELEM(gAssetManagerMethods));
}
}; // namespace android
diff --git a/core/jni/include/android_runtime/android_util_AssetManager.h b/core/jni/include/android_runtime/android_util_AssetManager.h
index 8dd9337..2c1e357 100644
--- a/core/jni/include/android_runtime/android_util_AssetManager.h
+++ b/core/jni/include/android_runtime/android_util_AssetManager.h
@@ -14,17 +14,20 @@
* limitations under the License.
*/
-#ifndef android_util_AssetManager_H
-#define android_util_AssetManager_H
+#ifndef ANDROID_RUNTIME_ASSETMANAGER_H
+#define ANDROID_RUNTIME_ASSETMANAGER_H
-#include <androidfw/AssetManager.h>
+#include "androidfw/AssetManager2.h"
+#include "androidfw/MutexGuard.h"
#include "jni.h"
namespace android {
-extern AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject assetMgr);
+extern AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager);
+extern Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager);
+extern Guarded<AssetManager2>* AssetManagerForNdkAssetManager(AAssetManager* assetmanager);
-}
+} // namespace android
-#endif
+#endif // ANDROID_RUNTIME_ASSETMANAGER_H
diff --git a/core/proto/android/app/notification.proto b/core/proto/android/app/notification.proto
index 5376b0e..379a4ae 100644
--- a/core/proto/android/app/notification.proto
+++ b/core/proto/android/app/notification.proto
@@ -18,6 +18,8 @@
option java_package = "android.app";
option java_multiple_files = true;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
package android.app;
/**
@@ -25,13 +27,15 @@
* Deprecated fields are not included in the proto.
*/
message NotificationProto {
- optional string channel_id = 1;
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional string channel_id = 1 [ (.android.privacy).dest = DEST_EXPLICIT ];
optional bool has_ticker_text = 2;
optional int32 flags = 3;
optional int32 color = 4;
- optional string category = 5;
- optional string group_key = 6;
- optional string sort_key = 7;
+ optional string category = 5 [ (.android.privacy).dest = DEST_EXPLICIT ];
+ optional string group_key = 6 [ (.android.privacy).dest = DEST_EXPLICIT ];
+ optional string sort_key = 7 [ (.android.privacy).dest = DEST_EXPLICIT ];
optional int32 action_length = 8;
// If this field is not set, then the value is unknown.
diff --git a/core/proto/android/app/profilerinfo.proto b/core/proto/android/app/profilerinfo.proto
index ca1b935..6b28318 100644
--- a/core/proto/android/app/profilerinfo.proto
+++ b/core/proto/android/app/profilerinfo.proto
@@ -18,12 +18,16 @@
option java_package = "android.app";
option java_multiple_files = true;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
package android.app;
/**
* An android.app.ProfilerInfo object.
*/
message ProfilerInfoProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string profile_file = 1;
optional int32 profile_fd = 2;
optional int32 sampling_interval = 3;
diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto
index 111b27f..a62d56c 100644
--- a/core/proto/android/content/configuration.proto
+++ b/core/proto/android/content/configuration.proto
@@ -22,11 +22,14 @@
import "frameworks/base/core/proto/android/app/window_configuration.proto";
import "frameworks/base/core/proto/android/content/locale.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
/**
* An android resource configuration.
*/
message ConfigurationProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional float font_scale = 1;
optional uint32 mcc = 2;
optional uint32 mnc = 3;
diff --git a/core/proto/android/content/featureinfo.proto b/core/proto/android/content/featureinfo.proto
index a750120..6878f0e 100644
--- a/core/proto/android/content/featureinfo.proto
+++ b/core/proto/android/content/featureinfo.proto
@@ -16,14 +16,20 @@
syntax = "proto2";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
option java_package = "android.content.pm";
option java_multiple_files = true;
package android.content.pm;
message FeatureInfoProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // Some hard coded feature name
optional string name = 1;
optional int32 version = 2;
+ // String representation of reqGlEsVersion.
optional string gles_version = 3;
optional int32 flags = 4;
}
diff --git a/core/proto/android/content/intent.proto b/core/proto/android/content/intent.proto
index 3e5265a..c25b46d 100644
--- a/core/proto/android/content/intent.proto
+++ b/core/proto/android/content/intent.proto
@@ -21,9 +21,12 @@
option java_multiple_files = true;
import "frameworks/base/core/proto/android/os/patternmatcher.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
// Next Tag: 13
message IntentProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum DockState {
// Used as an int value for Intent#EXTRA_DOCK_STATE to represent that
// the phone is not in any dock.
@@ -48,13 +51,13 @@
optional string action = 1;
repeated string categories = 2;
- optional string data = 3;
+ optional string data = 3 [ (.android.privacy).dest = DEST_EXPLICIT ];
optional string type = 4;
optional string flag = 5;
optional string package = 6;
optional string component = 7;
optional string source_bounds = 8;
- optional string clip_data = 9;
+ optional string clip_data = 9 [ (.android.privacy).dest = DEST_EXPLICIT ];
optional string extras = 10;
optional int32 content_user_hint = 11;
optional string selector = 12;
@@ -62,9 +65,11 @@
// Next Tag: 11
message IntentFilterProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
repeated string actions = 1;
repeated string categories = 2;
- repeated string data_schemes = 3;
+ repeated string data_schemes = 3 [ (.android.privacy).dest = DEST_EXPLICIT ];
repeated android.os.PatternMatcherProto data_scheme_specs = 4;
repeated AuthorityEntryProto data_authorities = 5;
repeated android.os.PatternMatcherProto data_paths = 6;
@@ -75,6 +80,8 @@
}
message AuthorityEntryProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string host = 1;
optional bool wild = 2;
optional int32 port = 3;
diff --git a/core/proto/android/content/locale.proto b/core/proto/android/content/locale.proto
index f0de31c..2be3ab9 100644
--- a/core/proto/android/content/locale.proto
+++ b/core/proto/android/content/locale.proto
@@ -18,9 +18,13 @@
option java_package = "android.content";
option java_multiple_files = true;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
package android.content;
message LocaleProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string language = 1;
optional string country = 2;
optional string variant = 3;
diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto
index 8470159..6e99bec 100644
--- a/core/proto/android/content/package_item_info.proto
+++ b/core/proto/android/content/package_item_info.proto
@@ -18,9 +18,13 @@
option java_package = "android.content.pm";
option java_multiple_files = true;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
package android.content.pm;
message PackageItemInfoProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
optional string package_name = 2;
optional int32 label_res = 3;
@@ -31,6 +35,8 @@
// Proto of android.content.pm.ApplicationInfo which extends PackageItemInfo
message ApplicationInfoProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional PackageItemInfoProto package = 1;
optional string permission = 2;
optional string process_name = 3;
@@ -48,6 +54,8 @@
repeated string split_class_loader_names = 15;
message Version {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool enabled = 1;
optional int32 min_sdk_version = 2;
optional int32 target_sdk_version = 3;
@@ -57,6 +65,8 @@
optional Version version = 16;
message Detail {
+ option (.android.msg_privacy).dest = DEST_EXPLICIT;
+
optional string class_name = 1;
optional string task_affinity = 2;
optional int32 requires_smallest_width_dp = 3;
diff --git a/core/proto/android/graphics/point.proto b/core/proto/android/graphics/point.proto
index 5ae17cb..035b9fe 100644
--- a/core/proto/android/graphics/point.proto
+++ b/core/proto/android/graphics/point.proto
@@ -17,9 +17,13 @@
syntax = "proto2";
package android.graphics;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
option java_multiple_files = true;
message PointProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 x = 1;
optional int32 y = 2;
}
diff --git a/core/proto/android/graphics/rect.proto b/core/proto/android/graphics/rect.proto
index 562ffce..eb403fe 100644
--- a/core/proto/android/graphics/rect.proto
+++ b/core/proto/android/graphics/rect.proto
@@ -17,9 +17,13 @@
syntax = "proto2";
package android.graphics;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
option java_multiple_files = true;
message RectProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 left = 1;
optional int32 top = 2;
optional int32 right = 3;
diff --git a/core/proto/android/os/cpufreq.proto b/core/proto/android/os/cpufreq.proto
index a8da0bf..8481ffc 100644
--- a/core/proto/android/os/cpufreq.proto
+++ b/core/proto/android/os/cpufreq.proto
@@ -18,10 +18,13 @@
option java_multiple_files = true;
option java_outer_classname = "CpuFreqProto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
package android.os;
// cpu frequency time from /sys/devices/system/cpu/cpufreq/all_time_in_state
message CpuFreq {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
optional int32 jiffy_hz = 1; // obtain by system config.
@@ -30,10 +33,13 @@
// frequency time pre cpu, unit in jiffy, TODO: obtain jiffies.
message CpuFreqStats {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
optional string cpu_name = 1;
message TimeInState {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 state_khz = 1; // cpu frequency
optional int64 time_jiffy = 2; // number of jiffies
}
diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto
index cd151e2..ca43602 100644
--- a/core/proto/android/os/cpuinfo.proto
+++ b/core/proto/android/os/cpuinfo.proto
@@ -18,6 +18,8 @@
option java_multiple_files = true;
option java_outer_classname = "CpuInfoProto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
package android.os;
/**
@@ -27,8 +29,11 @@
* Next Tag: 6
*/
message CpuInfo {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
message TaskStats {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 total = 1; // total number of cpu tasks
optional int32 running = 2; // number of running tasks
optional int32 sleeping = 3; // number of sleeping tasks
@@ -38,6 +43,8 @@
optional TaskStats task_stats = 1;
message MemStats { // unit in kB
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 total = 1;
optional int32 used = 2;
optional int32 free = 3;
@@ -48,6 +55,8 @@
optional MemStats swap = 3;
message CpuUsage { // unit is percentage %
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 cpu = 1; // 400% cpu indicates 4 cores
optional int32 user = 2;
optional int32 nice = 3;
@@ -62,9 +71,11 @@
// Next Tag: 13
message Task {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 pid = 1;
optional int32 tid = 2;
- optional string user = 3;
+ optional string user = 3; // the process name which uses cpu
optional string pr = 4; // priority of each task, using string type is because special value RT (real time)
optional sint32 ni = 5; // niceness value
optional float cpu = 6; // precentage of cpu usage of the task
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index 7e5be9d..c296dab 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -18,15 +18,21 @@
option java_multiple_files = true;
option java_outer_classname = "WakeupSourcesProto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
package android.os;
message KernelWakeSources {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Kernel records of what caused the application processor to wake up
repeated WakeupSourceProto wakeup_sources = 1;
}
// Next Tag: 11
message WakeupSourceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Name of the event which triggers application processor
optional string name = 1;
diff --git a/core/proto/android/os/looper.proto b/core/proto/android/os/looper.proto
index ef84bb1..435c648 100644
--- a/core/proto/android/os/looper.proto
+++ b/core/proto/android/os/looper.proto
@@ -20,9 +20,12 @@
option java_multiple_files = true;
import "frameworks/base/core/proto/android/os/messagequeue.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
message LooperProto {
- optional string thread_name = 1;
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional string thread_name = 1 [ (.android.privacy).dest = DEST_EXPLICIT ];
optional int64 thread_id = 2;
optional int32 identity_hash_code = 3;
optional android.os.MessageQueueProto queue = 4;
diff --git a/core/proto/android/os/message.proto b/core/proto/android/os/message.proto
index 38e27a1..048d031 100644
--- a/core/proto/android/os/message.proto
+++ b/core/proto/android/os/message.proto
@@ -17,9 +17,12 @@
syntax = "proto2";
package android.os;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
option java_multiple_files = true;
message MessageProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int64 when = 1;
// Name of callback class.
optional string callback = 2;
@@ -29,7 +32,7 @@
optional int32 arg1 = 4;
optional int32 arg2 = 5;
// String representation of an arbitrary object to send to the recipient.
- optional string obj = 6;
+ optional string obj = 6 [ (.android.privacy).dest = DEST_EXPLICIT ];
// Name of target class.
optional string target = 7;
optional int32 barrier = 8;
diff --git a/core/proto/android/os/messagequeue.proto b/core/proto/android/os/messagequeue.proto
index 5d4bff0..4bfcb81 100644
--- a/core/proto/android/os/messagequeue.proto
+++ b/core/proto/android/os/messagequeue.proto
@@ -20,8 +20,11 @@
option java_multiple_files = true;
import "frameworks/base/core/proto/android/os/message.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
message MessageQueueProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
repeated android.os.MessageProto messages = 1;
optional bool is_polling_locked = 2;
optional bool is_quitting = 3;
diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto
index f82ea76..b8f618b 100644
--- a/core/proto/android/os/pagetypeinfo.proto
+++ b/core/proto/android/os/pagetypeinfo.proto
@@ -18,6 +18,8 @@
option java_multiple_files = true;
option java_outer_classname = "PageTypeInfoProto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
package android.os;
/*
@@ -36,6 +38,7 @@
* Next tag: 5
*/
message PageTypeInfo {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
optional int32 page_block_order = 1;
@@ -48,6 +51,7 @@
// Next tag: 5
message MigrateTypeProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
optional int32 node = 1;
@@ -61,6 +65,7 @@
// Next tag: 9
message BlockProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
optional int32 node = 1;
diff --git a/core/proto/android/os/patternmatcher.proto b/core/proto/android/os/patternmatcher.proto
index d30315b..520f2f5 100644
--- a/core/proto/android/os/patternmatcher.proto
+++ b/core/proto/android/os/patternmatcher.proto
@@ -16,10 +16,13 @@
syntax = "proto2";
option java_multiple_files = true;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
package android.os;
message PatternMatcherProto {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
optional string pattern = 1;
enum Type {
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index 07b9ad0..694b94b 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -134,6 +134,8 @@
optional bool hal_instrumentation_enable = 11;
message InitSvc {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum Status {
STATUS_UNKNOWN = 0;
STATUS_RUNNING = 1;
@@ -230,7 +232,7 @@
// Read only properites on the device.
message Ro {
- optional bool adb_secure = 1;
+ optional bool adb_secure = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional string arch = 2;
optional bool audio_ignore_effects = 3;
optional bool audio_monitorRotation = 4;
@@ -265,6 +267,8 @@
// boot.img's properties.
message BootImage {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// When the boot.img is built.
optional string build_date = 1;
// UTC timestamp of build date.
@@ -278,12 +282,14 @@
optional BootImage bootimage = 8;
// Version of bootloader on device.
- optional string bootloader = 9;
+ optional string bootloader = 9 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Kernel bootmode, e.g. charger.
- optional string bootmode = 10;
+ optional string bootmode = 10 [ (android.privacy).dest = DEST_AUTOMATIC ];
// Android Platform build metadata.
message Build {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string date = 1;
optional int64 date_utc = 2;
optional string description = 3;
@@ -297,6 +303,8 @@
optional string user = 11;
message Version {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string base_os = 1;
optional string codename = 2;
optional string incremental = 3;
@@ -313,10 +321,10 @@
}
optional Build build = 11;
- optional bool camera_notify_nfc = 12;
+ optional bool camera_notify_nfc = 12 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional string carrier = 13;
- optional bool com_android_dataroaming = 14;
- optional bool com_android_prov_mobiledata = 15;
+ optional bool com_android_dataroaming = 14 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional bool com_android_prov_mobiledata = 15 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional string com_google_clientidbase = 16;
message Config {
@@ -341,6 +349,8 @@
optional string gfx_driver_0 = 26;
message Hardware {
+ option (android.msg_privacy).dest = DEST_LOCAL;
+
optional string value = 1; // value of ro.hardware itself
optional string activity_recognition = 2;
@@ -392,6 +402,8 @@
optional int32 opengles_version = 31;
message Product {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string board = 1;
optional string brand = 2;
optional string cpu_abi = 3;
@@ -405,6 +417,8 @@
optional string name = 11;
message Vendor {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string brand = 1;
optional string device = 2;
optional string manufacturer = 3;
diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto
index fd28322..27fbb24 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -394,8 +394,9 @@
optional SettingProto wifi_connected_mac_randomization_enabled = 350;
optional SettingProto show_restart_in_crash_dialog = 351;
optional SettingProto show_mute_in_crash_dialog = 352;
+ optional SettingProto chained_battery_attribution_enabled = 353;
- // Next tag = 353;
+ // Next tag = 354;
}
message SecureSettingsProto {
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 1434d82..39c5ec7 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -32,10 +32,13 @@
import "frameworks/base/core/proto/android/server/intentresolver.proto";
import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
import "frameworks/base/core/proto/android/util/common.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
option java_multiple_files = true;
message ActivityManagerServiceProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional ActivityStackSupervisorProto activities = 1;
optional BroadcastProto broadcasts = 2;
@@ -47,6 +50,8 @@
// "dumpsys activity --proto activities"
message ActivityStackSupervisorProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
repeated ActivityDisplayProto displays = 2;
optional KeyguardControllerProto keyguard_controller = 3;
@@ -56,12 +61,16 @@
/* represents ActivityStackSupervisor.ActivityDisplay */
message ActivityDisplayProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
optional int32 id = 2;
repeated ActivityStackProto stacks = 3;
}
message ActivityStackProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
optional int32 id = 2;
repeated TaskRecordProto tasks = 3;
@@ -72,6 +81,8 @@
}
message TaskRecordProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
optional int32 id = 2;
repeated ActivityRecordProto activities = 3;
@@ -88,6 +99,8 @@
}
message ActivityRecordProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .com.android.server.wm.proto.ConfigurationContainerProto configuration_container = 1;
optional .com.android.server.wm.proto.IdentifierProto identifier = 2;
optional string state = 3;
@@ -97,12 +110,16 @@
}
message KeyguardControllerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool keyguard_showing = 1;
optional bool keyguard_occluded = 2;
}
// "dumpsys activity --proto broadcasts"
message BroadcastProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
repeated ReceiverListProto receiver_list = 1;
optional .com.android.server.IntentResolverProto receiver_resolver = 2;
@@ -119,6 +136,8 @@
}
message ReceiverListProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional ProcessRecordProto app = 1;
optional int32 pid = 2;
optional int32 uid = 3;
@@ -130,6 +149,8 @@
}
message ProcessRecordProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 pid = 1;
optional string process_name = 2;
optional int32 uid = 3;
@@ -140,11 +161,15 @@
}
message BroadcastRecordProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 user_id = 1;
optional string intent_action = 2;
}
message BroadcastFilterProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .android.content.IntentFilterProto intent_filter = 1;
optional string required_permission = 2;
optional string hex_hash = 3; // used to find the object in IntentResolver
@@ -152,6 +177,8 @@
}
message BroadcastQueueProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string queue_name = 1;
repeated BroadcastRecordProto parallel_broadcasts = 2;
repeated BroadcastRecordProto ordered_broadcasts = 3;
@@ -159,6 +186,8 @@
repeated BroadcastRecordProto historical_broadcasts = 5;
message BroadcastSummary {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .android.content.IntentProto intent = 1;
optional int64 enqueue_clock_time_ms = 2;
optional int64 dispatch_clock_time_ms = 3;
@@ -168,14 +197,20 @@
}
message MemInfoProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int64 uptime_duration_ms = 1;
optional int64 elapsed_realtime_ms = 2;
message ProcessMemory {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 pid = 1;
optional string process_name = 2;
message MemoryInfo {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
// The proportional set size for the heap.
optional int32 total_pss_kb = 2;
@@ -197,6 +232,8 @@
}
}
message HeapInfo {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional MemoryInfo mem_info = 1;
optional int32 heap_size_kb = 2;
optional int32 heap_alloc_kb = 3;
@@ -212,6 +249,8 @@
repeated MemoryInfo dalvik_details = 8;
message AppSummary {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 java_heap_pss_kb = 1;
optional int32 native_heap_pss_kb = 2;
optional int32 code_pss_kb = 3;
@@ -230,9 +269,13 @@
repeated ProcessMemory native_processes = 3;
message AppData {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional ProcessMemory process_memory = 1;
message ObjectStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 view_instance_count = 1;
optional int32 view_root_instance_count = 2;
optional int32 app_context_instance_count = 3;
@@ -250,11 +293,15 @@
optional ObjectStats objects = 2;
message SqlStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 memory_used_kb = 1;
optional int32 pagecache_overflow_kb = 2;
optional int32 malloc_size_kb = 3;
message Database {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
optional int32 page_size = 2;
optional int32 db_size = 3;
@@ -274,6 +321,8 @@
repeated AppData app_processes = 4;
message MemItem {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string tag = 1;
optional string label = 2;
optional int32 id = 3;
@@ -337,9 +386,13 @@
}
message StickyBroadcastProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 user = 1;
message StickyAction {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
repeated .android.content.IntentProto intents = 2;
}
@@ -348,6 +401,7 @@
// "dumpsys activity --proto service"
message ActiveServicesProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
message ServicesByUser {
optional int32 user_id = 1;
@@ -358,11 +412,15 @@
// corresponds to ActivityManagerService.GrantUri Java class
message GrantUriProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 source_user_id = 1;
- optional string uri = 2;
+ optional string uri = 2 [ (.android.privacy).dest = DEST_EXPLICIT ];
}
message NeededUriGrantsProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string target_package = 1;
optional int32 target_uid = 2;
optional int32 flags = 3;
@@ -371,12 +429,16 @@
}
message UriPermissionOwnerProto {
+ option (.android.msg_privacy).dest = DEST_EXPLICIT;
+
optional string owner = 1;
repeated GrantUriProto read_perms = 2;
repeated GrantUriProto write_perms = 3;
}
message ServiceRecordProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string short_name = 1;
optional string hex_hash = 2;
optional bool is_running = 3; // false if the application service is null
@@ -387,6 +449,8 @@
optional string permission = 8;
message AppInfo {
+ option (.android.msg_privacy).dest = DEST_EXPLICIT;
+
optional string base_dir = 1;
optional string res_dir = 2;
optional string data_dir = 3;
@@ -398,6 +462,8 @@
optional bool delayed = 13;
message Foreground {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 id = 1;
optional .android.app.NotificationProto notification = 2;
}
@@ -411,6 +477,8 @@
// variables used to track states related to service start
message Start {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool start_requested = 1;
optional bool delayed_stop = 2;
optional bool stop_if_killed = 3;
@@ -420,6 +488,8 @@
optional Start start = 20;
message ExecuteNesting {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 execute_nesting = 1;
optional bool execute_fg = 2;
optional .android.util.Duration executing_start = 3;
@@ -429,6 +499,8 @@
optional .android.util.Duration destory_time = 22;
message Crash {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 restart_count = 1;
optional .android.util.Duration restart_delay = 2;
optional .android.util.Duration next_restart_time = 3;
@@ -437,6 +509,8 @@
optional Crash crash = 23;
message StartItemProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 id = 1;
optional .android.util.Duration duration = 2;
optional int32 delivery_count = 3;
@@ -453,6 +527,8 @@
}
message ConnectionRecordProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string hex_hash = 1;
optional int32 user_id = 2;
@@ -480,12 +556,16 @@
}
message AppBindRecordProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string hex_hash = 1;
optional ProcessRecordProto client = 2;
repeated ConnectionRecordProto connections = 3;
}
message IntentBindRecordProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string hex_hash = 1;
optional bool is_create = 2;
optional .android.content.IntentProto intent = 3;
@@ -500,6 +580,8 @@
// TODO: "dumpsys activity --proto processes"
message ProcessesProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
repeated ProcessRecordProto procs = 1;
repeated ProcessRecordProto isolated_procs = 2;
repeated ActiveInstrumentationProto active_instrumentations = 3;
@@ -508,6 +590,8 @@
// Process LRU list (sorted by oom_adj)
message LruProcesses {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 size = 1;
optional int32 non_act_at = 2;
optional int32 non_svc_at = 3;
@@ -538,18 +622,24 @@
optional bool config_will_change = 21;
message ScreenCompatPackage {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string package = 1;
optional int32 mode = 2;
}
repeated ScreenCompatPackage screen_compat_packages = 22;
message UidObserverRegistrationProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 uid = 1;
optional string package = 2;
repeated .android.app.UidObserverFlag flags = 3;
optional int32 cut_point = 4; // only available when UID_OBSERVER_PROCSTATE is on
message ProcState {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 uid = 1;
optional int32 state = 2;
}
@@ -560,6 +650,8 @@
repeated int32 device_idle_temp_whitelist = 25;
message PendingTempWhitelist {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 target_uid = 1;
optional int64 duration_ms = 2;
optional string tag = 3;
@@ -567,6 +659,8 @@
repeated PendingTempWhitelist pending_temp_whitelist = 26;
message SleepStatus {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .android.os.PowerManagerInternalProto.Wakefulness wakefulness = 1;
repeated string sleep_tokens = 2;
optional bool sleeping = 3;
@@ -576,12 +670,16 @@
optional SleepStatus sleep_status = 27;
message VoiceProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string session = 1;
optional .android.os.PowerManagerProto.WakeLockProto wakelock = 2;
}
optional VoiceProto running_voice = 28;
message VrControllerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum VrMode {
FLAG_NON_VR_MODE = 0;
FLAG_VR_MODE = 1;
@@ -593,6 +691,8 @@
optional VrControllerProto vr_controller = 29;
message DebugApp {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string debug_app = 1;
optional string orig_debug_app = 2;
optional bool debug_transient = 3;
@@ -602,10 +702,16 @@
optional AppTimeTrackerProto current_tracker = 31;
message MemWatchProcess {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
message Process {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
message MemStats {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 uid = 1;
optional string size = 2;
optional string report_to = 3;
@@ -615,8 +721,10 @@
repeated Process procs = 1;
message Dump {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string proc_name = 1;
- optional string file = 2;
+ optional string file = 2 [ (.android.privacy).dest = DEST_EXPLICIT ];
optional int32 pid = 3;
optional int32 uid = 4;
}
@@ -626,6 +734,8 @@
optional string track_allocation_app = 33;
message Profile {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string app_name = 1;
optional ProcessRecordProto proc = 2;
optional .android.app.ProfilerInfoProto info = 3;
@@ -636,6 +746,8 @@
optional bool always_finish_activities = 36;
message Controller {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string controller = 1;
optional bool is_a_monkey = 2;
}
@@ -666,6 +778,8 @@
}
message ActiveInstrumentationProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .android.content.ComponentNameProto class = 1;
optional bool finished = 2;
repeated ProcessRecordProto running_processes = 3;
@@ -674,11 +788,13 @@
optional string profile_file = 6;
optional string watcher = 7;
optional string ui_automation_connection = 8;
- optional string arguments = 9;
+ optional string arguments = 9 [ (.android.privacy).dest = DEST_EXPLICIT ];
}
// Proto definition of com.android.server.am.UidRecord.java
message UidRecordProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string hex_hash = 1;
optional int32 uid = 2;
optional .android.app.ProcessState current = 3;
@@ -699,6 +815,8 @@
optional int32 num_procs = 10;
message ProcStateSequence {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int64 cururent = 1;
optional int64 last_network_updated = 2;
optional int64 last_dispatched = 3;
@@ -708,12 +826,16 @@
// proto of class ImportanceToken in ActivityManagerService
message ImportanceTokenProto {
+ option (.android.msg_privacy).dest = DEST_EXPLICIT;
+
optional int32 pid = 1;
optional string token = 2;
optional string reason = 3;
}
message ProcessOomProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool persistent = 1;
optional int32 num = 2;
optional string oom_adj = 3;
@@ -749,6 +871,8 @@
}
message Detail {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 max_adj = 1;
optional int32 cur_raw_adj = 2;
optional int32 set_raw_adj = 3;
@@ -765,6 +889,8 @@
// only make sense if process is a service
message CpuRunTime {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int64 over_ms = 1;
optional int64 used_ms = 2;
optional float ultilization = 3; // ratio of cpu time usage
@@ -775,6 +901,8 @@
}
message ProcessToGcProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional ProcessRecordProto proc = 1;
optional bool report_low_memory = 2;
optional int64 now_uptime_ms = 3;
@@ -784,13 +912,18 @@
// sync with com.android.server.am.AppErrors.java
message AppErrorsProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional int64 now_uptime_ms = 1;
message ProcessCrashTime {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string process_name = 1;
message Entry {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 uid = 1;
optional int64 last_crashed_at_ms = 2;
}
@@ -799,14 +932,18 @@
repeated ProcessCrashTime process_crash_times = 2;
message BadProcess {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string process_name = 1;
message Entry {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 uid = 1;
optional int64 crashed_at_ms = 2;
optional string short_msg = 3;
- optional string long_msg = 4;
- optional string stack = 5;
+ optional string long_msg = 4 [ (.android.privacy).dest = DEST_EXPLICIT ];
+ optional string stack = 5 [ (.android.privacy).dest = DEST_EXPLICIT ];
}
repeated Entry entries = 2;
}
@@ -815,6 +952,8 @@
// sync with com.android.server.am.UserState.java
message UserStateProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum State {
STATE_BOOTING = 0;
STATE_RUNNING_LOCKED = 1;
@@ -829,7 +968,11 @@
// sync with com.android.server.am.UserController.java
message UserControllerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
message User {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 id = 1;
optional UserStateProto state = 2;
}
@@ -838,6 +981,8 @@
repeated int32 user_lru = 3;
message UserProfile {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 user = 1;
optional int32 profile = 2;
}
@@ -846,6 +991,8 @@
// sync with com.android.server.am.AppTimeTracker.java
message AppTimeTrackerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string receiver = 1;
optional int64 total_duration_ms = 2;
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto
index 87d302e..53b4be4 100644
--- a/core/proto/android/server/alarmmanagerservice.proto
+++ b/core/proto/android/server/alarmmanagerservice.proto
@@ -47,6 +47,8 @@
// Only valid if is_interactive is false.
optional int64 time_until_next_non_wakeup_delivery_ms = 11;
+ // Can be negative if the non-wakeup alarm time is in the past (non-wakeup
+ // alarms aren't delivered unil the next time the device wakes up).
optional int64 time_until_next_non_wakeup_alarm_ms = 12;
optional int64 time_until_next_wakeup_ms = 13;
optional int64 time_since_last_wakeup_ms = 14;
diff --git a/core/proto/android/server/appwindowthumbnail.proto b/core/proto/android/server/appwindowthumbnail.proto
index e67b854..8f48d75 100644
--- a/core/proto/android/server/appwindowthumbnail.proto
+++ b/core/proto/android/server/appwindowthumbnail.proto
@@ -17,6 +17,7 @@
syntax = "proto2";
import "frameworks/base/core/proto/android/server/surfaceanimator.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
package com.android.server.wm.proto;
option java_multiple_files = true;
@@ -25,6 +26,8 @@
* Represents a {@link com.android.server.wm.AppWindowThumbnail} object.
*/
message AppWindowThumbnailProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 width = 1;
optional int32 height = 2;
optional SurfaceAnimatorProto surface_animator = 3;
diff --git a/core/proto/android/server/fingerprint.proto b/core/proto/android/server/fingerprint.proto
index ec4ffe0..2a7fbc3 100644
--- a/core/proto/android/server/fingerprint.proto
+++ b/core/proto/android/server/fingerprint.proto
@@ -17,15 +17,21 @@
syntax = "proto2";
package com.android.server.fingerprint;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
option java_multiple_files = true;
option java_outer_classname = "FingerprintServiceProto";
message FingerprintServiceDumpProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Each log may include multiple tuples of (user_id, num_fingerprints).
repeated FingerprintUserStatsProto users = 1;
}
message FingerprintUserStatsProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Should be 0, 10, 11, 12, etc. where 0 is the owner.
optional int32 user_id = 1;
@@ -42,6 +48,8 @@
// A com.android.server.fingerprint.FingerpintService.PerformanceStats object.
message PerformanceStatsProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Number of accepted fingerprints.
optional int32 accept = 1;
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index c1bd692..f3ebd41 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -28,13 +28,20 @@
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto";
import "frameworks/base/core/proto/android/view/display.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
message PowerManagerServiceDumpProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
// A com.android.server.power.PowerManagerService.Constants object.
message ConstantsProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool is_no_cached_wake_locks = 1;
}
message ActiveWakeLocksProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool is_cpu = 1;
optional bool is_screen_bright = 2;
optional bool is_screen_dim = 3;
@@ -46,12 +53,16 @@
optional bool is_draw = 8;
}
message UserActivityProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool is_screen_bright = 1;
optional bool is_screen_dim = 2;
optional bool is_screen_dream = 3;
}
// A com.android.server.power.PowerManagerService.UidState object.
message UidStateProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 uid = 1;
optional string uid_string = 2;
optional bool is_active = 3;
@@ -167,13 +178,19 @@
// A com.android.server.power.PowerManagerService.SuspendBlockerImpl object.
message SuspendBlockerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
optional int32 reference_count = 2;
}
// A com.android.server.power.PowerManagerService.WakeLock object.
message WakeLockProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
message WakeLockFlagsProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Turn the screen on when the wake lock is acquired.
optional bool is_acquire_causes_wakeup = 1;
// When this wake lock is released, poke the user activity timer
@@ -182,7 +199,7 @@
}
optional .android.os.PowerManagerProto.WakeLockLevel lock_level = 1;
- optional string tag = 2;
+ optional string tag = 2 [ (.android.privacy).dest = DEST_EXPLICIT ];
optional WakeLockFlagsProto flags = 3;
optional bool is_disabled = 4;
// Acquire time in ms
@@ -196,12 +213,18 @@
}
message PowerServiceSettingsAndConfigurationDumpProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
message StayOnWhilePluggedInProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool is_stay_on_while_plugged_in_ac = 1;
optional bool is_stay_on_while_plugged_in_usb = 2;
optional bool is_stay_on_while_plugged_in_wireless = 3;
}
message ScreenBrightnessSettingLimitsProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 setting_minimum = 1;
optional int32 setting_maximum = 2;
optional int32 setting_default = 3;
diff --git a/core/proto/android/server/surfaceanimator.proto b/core/proto/android/server/surfaceanimator.proto
index 60713d7..7f7839e 100644
--- a/core/proto/android/server/surfaceanimator.proto
+++ b/core/proto/android/server/surfaceanimator.proto
@@ -17,6 +17,7 @@
syntax = "proto2";
import "frameworks/base/core/proto/android/view/surfacecontrol.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
package com.android.server.wm.proto;
option java_multiple_files = true;
@@ -25,6 +26,8 @@
* Represents a {@link com.android.server.wm.SurfaceAnimator} object.
*/
message SurfaceAnimatorProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string animation_adapter = 1;
optional .android.view.SurfaceControlProto leash = 2;
optional bool animation_start_delayed = 3;
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 71f33c7..c11058a 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -26,12 +26,15 @@
import "frameworks/base/core/proto/android/view/displayinfo.proto";
import "frameworks/base/core/proto/android/view/surface.proto";
import "frameworks/base/core/proto/android/view/windowlayoutparams.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
package com.android.server.wm.proto;
option java_multiple_files = true;
message WindowManagerServiceProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional WindowManagerPolicyProto policy = 1;
/* window hierarchy root */
optional RootWindowContainerProto root_window_container = 2;
@@ -46,6 +49,8 @@
/* represents DisplayContent */
message RootWindowContainerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional WindowContainerProto window_container = 1;
repeated DisplayProto displays = 2;
/* window references in top down z order */
@@ -53,16 +58,22 @@
}
message BarControllerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .android.app.StatusBarManagerProto.WindowState state = 1;
optional .android.app.StatusBarManagerProto.TransientWindowState transient_state = 2;
}
message WindowOrientationListenerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool enabled = 1;
optional .android.view.SurfaceProto.Rotation rotation = 2;
}
message KeyguardServiceDelegateProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool showing = 1;
optional bool occluded = 2;
optional bool secure = 3;
@@ -84,6 +95,8 @@
/* represents PhoneWindowManager */
message WindowManagerPolicyProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 last_system_ui_flags = 1;
enum UserRotationMode {
USER_ROTATION_FREE = 0;
@@ -112,6 +125,8 @@
/* represents AppTransition */
message AppTransitionProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum AppState {
APP_STATE_IDLE = 0;
APP_STATE_READY = 1;
@@ -147,6 +162,8 @@
/* represents DisplayContent */
message DisplayProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional WindowContainerProto window_container = 1;
optional int32 id = 2;
repeated StackProto stacks = 3;
@@ -165,22 +182,30 @@
/* represents DisplayFrames */
message DisplayFramesProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .android.graphics.RectProto stable_bounds = 1;
}
/* represents DockedStackDividerController */
message DockedStackDividerControllerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool minimized_dock = 1;
}
/* represents PinnedStackController */
message PinnedStackControllerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .android.graphics.RectProto default_bounds = 1;
optional .android.graphics.RectProto movement_bounds = 2;
}
/* represents TaskStack */
message StackProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional WindowContainerProto window_container = 1;
optional int32 id = 2;
repeated TaskProto tasks = 3;
@@ -197,6 +222,8 @@
/* represents Task */
message TaskProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional WindowContainerProto window_container = 1;
optional int32 id = 2;
repeated AppWindowTokenProto app_window_tokens = 3;
@@ -208,8 +235,10 @@
/* represents AppWindowToken */
message AppWindowTokenProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
/* obtained from ActivityRecord */
- optional string name = 1;
+ optional string name = 1 [ (.android.privacy).dest = DEST_EXPLICIT ];
optional WindowTokenProto window_token = 2;
optional bool last_surface_showing = 3;
optional bool is_waiting_for_transition_start = 4;
@@ -236,6 +265,8 @@
/* represents WindowToken */
message WindowTokenProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional WindowContainerProto window_container = 1;
optional int32 hash_code = 2;
repeated WindowStateProto windows = 3;
@@ -246,6 +277,8 @@
/* represents WindowState */
message WindowStateProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional WindowContainerProto window_container = 1;
optional IdentifierProto identifier = 2;
optional int32 display_id = 3;
@@ -287,13 +320,17 @@
}
message IdentifierProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 hash_code = 1;
optional int32 user_id = 2;
- optional string title = 3;
+ optional string title = 3 [ (.android.privacy).dest = DEST_EXPLICIT ];
}
/* represents WindowStateAnimator */
message WindowStateAnimatorProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .android.graphics.RectProto last_clip_rect = 1;
optional WindowSurfaceControllerProto surface = 2;
enum DrawState {
@@ -309,18 +346,24 @@
/* represents WindowSurfaceController */
message WindowSurfaceControllerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool shown = 1;
optional int32 layer = 2;
}
/* represents ScreenRotationAnimation */
message ScreenRotationAnimationProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional bool started = 1;
optional bool animation_running = 2;
}
/* represents WindowContainer */
message WindowContainerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional ConfigurationContainerProto configuration_container = 1;
optional int32 orientation = 2;
optional bool visible = 3;
@@ -329,6 +372,8 @@
/* represents ConfigurationContainer */
message ConfigurationContainerProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional .android.content.ConfigurationProto override_configuration = 1;
optional .android.content.ConfigurationProto full_configuration = 2;
optional .android.content.ConfigurationProto merged_override_configuration = 3;
diff --git a/core/proto/android/service/diskstats.proto b/core/proto/android/service/diskstats.proto
index 3c7a0e3..3d7ee91 100644
--- a/core/proto/android/service/diskstats.proto
+++ b/core/proto/android/service/diskstats.proto
@@ -17,10 +17,14 @@
syntax = "proto2";
package android.service.diskstats;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
option java_multiple_files = true;
option java_outer_classname = "DiskStatsServiceProto";
message DiskStatsServiceDumpProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum EncryptionType {
// Unknown encryption type
ENCRYPTION_UNKNOWN = 0;
@@ -34,7 +38,7 @@
// Whether the latency test resulted in an error
optional bool has_test_error = 1;
// If the test errored, error message is contained here
- optional string error_message = 2;
+ optional string error_message = 2 [ (android.privacy).dest = DEST_EXPLICIT ];
// 512B write latency in milliseconds, if the test was successful
optional int32 write_512b_latency_millis = 3;
// Free Space in the major partitions
@@ -48,6 +52,8 @@
}
message DiskStatsCachedValuesProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Total app code size, in kilobytes
optional int64 agg_apps_size = 1;
// Total app cache size, in kilobytes
@@ -71,6 +77,8 @@
}
message DiskStatsAppSizesProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Name of the package
optional string package_name = 1;
// App's code size in kilobytes
@@ -82,6 +90,8 @@
}
message DiskStatsFreeSpaceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum Folder {
// Data folder
FOLDER_DATA = 0;
diff --git a/core/proto/android/service/netstats.proto b/core/proto/android/service/netstats.proto
index ad9191c..29fd195 100644
--- a/core/proto/android/service/netstats.proto
+++ b/core/proto/android/service/netstats.proto
@@ -17,11 +17,15 @@
syntax = "proto2";
package android.service;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
option java_multiple_files = true;
option java_outer_classname = "NetworkStatsServiceProto";
// Represents dumpsys from NetworkStatsService (netstats).
message NetworkStatsServiceDumpProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
repeated NetworkInterfaceProto active_interfaces = 1;
repeated NetworkInterfaceProto active_uid_interfaces = 2;
@@ -41,6 +45,8 @@
// Corresponds to NetworkStatsService.mActiveIfaces/mActiveUidIfaces.
message NetworkInterfaceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string interface = 1;
optional NetworkIdentitySetProto identities = 2;
@@ -48,17 +54,21 @@
// Corresponds to NetworkIdentitySet.
message NetworkIdentitySetProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
repeated NetworkIdentityProto identities = 1;
}
// Corresponds to NetworkIdentity.
message NetworkIdentityProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Constats from ConnectivityManager.TYPE_*.
optional int32 type = 1;
- optional string subscriber_id = 2;
+ optional string subscriber_id = 2 [ (android.privacy).dest = DEST_EXPLICIT ];
- optional string network_id = 3;
+ optional string network_id = 3 [ (android.privacy).dest = DEST_EXPLICIT ];
optional bool roaming = 4;
@@ -69,6 +79,8 @@
// Corresponds to NetworkStatsRecorder.
message NetworkStatsRecorderProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int64 pending_total_bytes = 1;
optional NetworkStatsCollectionProto complete_history = 2;
@@ -76,11 +88,15 @@
// Corresponds to NetworkStatsCollection.
message NetworkStatsCollectionProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
repeated NetworkStatsCollectionStatsProto stats = 1;
}
// Corresponds to NetworkStatsCollection.mStats.
message NetworkStatsCollectionStatsProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional NetworkStatsCollectionKeyProto key = 1;
optional NetworkStatsHistoryProto history = 2;
@@ -88,6 +104,8 @@
// Corresponds to NetworkStatsCollection.Key.
message NetworkStatsCollectionKeyProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional NetworkIdentitySetProto identity = 1;
optional int32 uid = 2;
@@ -99,6 +117,8 @@
// Corresponds to NetworkStatsHistory.
message NetworkStatsHistoryProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Duration for this bucket in milliseconds.
optional int64 bucket_duration_ms = 1;
@@ -107,6 +127,8 @@
// Corresponds to each bucket in NetworkStatsHistory.
message NetworkStatsHistoryBucketProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Bucket start time in milliseconds since epoch.
optional int64 bucket_start_ms = 1;
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index aa1a575..ef777de 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -18,18 +18,25 @@
package android.service.pm;
import "frameworks/base/core/proto/android/content/featureinfo.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
option java_multiple_files = true;
option java_outer_classname = "PackageServiceProto";
message PackageServiceDumpProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
message PackageShortProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Name of package. e.g. "com.android.providers.telephony".
optional string name = 1;
// UID for this package as assigned by Android OS.
optional int32 uid = 2;
}
message SharedLibraryProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional string name = 1;
// True if library path is not null (jar), false otherwise (apk)
optional bool is_jar = 2;
@@ -39,8 +46,10 @@
optional string apk = 4;
}
message SharedUserProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 user_id = 1;
- optional string name = 2;
+ optional string name = 2 [ (android.privacy).dest = DEST_EXPLICIT ];
}
// Installed packages.
@@ -51,15 +60,22 @@
repeated PackageProto packages = 5;
repeated SharedUserProto shared_users = 6;
// Messages from the settings problem file
- repeated string messages = 7;
+ repeated string messages = 7 [ (android.privacy).dest = DEST_EXPLICIT ];
}
message PackageProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
message SplitProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The split name of package, e.g. base
optional string name = 1;
optional int32 revision_code = 2;
}
message UserInfoProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum InstallType {
NOT_INSTALLED_FOR_USER = 0;
FULL_APP_INSTALL = 1;
diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto
index b2e0373..4c11f1e 100644
--- a/core/proto/android/service/procstats.proto
+++ b/core/proto/android/service/procstats.proto
@@ -19,6 +19,7 @@
option java_outer_classname = "ProcessStatsServiceProto";
import "frameworks/base/core/proto/android/util/common.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
package android.service.procstats;
@@ -28,6 +29,7 @@
* Next Tag: 4
*/
message ProcessStatsServiceDumpProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
optional ProcessStatsSectionProto procstats_now = 1;
@@ -43,6 +45,7 @@
* Next Tag: 9
*/
message ProcessStatsSectionProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
// Elapsed realtime at start of report.
optional int64 start_realtime_ms = 1;
@@ -78,6 +81,7 @@
// Next Tag: 6
message ProcessStatsProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
// Name of process.
optional string process = 1;
@@ -87,6 +91,8 @@
// Information about how often kills occurred
message Kill {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
// Count of excessive CPU kills
optional int32 cpu = 1;
@@ -99,6 +105,8 @@
optional Kill kill = 3;
message State {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
enum ScreenState {
SCREEN_UNKNOWN = 0;
OFF = 1;
@@ -115,6 +123,8 @@
}
optional MemoryState memory_state = 2;
+ // this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java
+ // and not frameworks/base/core/java/android/app/ActivityManager.java
enum ProcessState {
PROCESS_UNKNOWN = 0;
// Persistent system process.
@@ -127,14 +137,14 @@
IMPORTANT_BACKGROUND = 4;
// Performing backup operation.
BACKUP = 5;
- // Heavy-weight process (currently not used).
- HEAVY_WEIGHT = 6;
// Background process running a service.
- SERVICE = 7;
+ SERVICE = 6;
// Process not running, but would be if there was enough RAM.
- SERVICE_RESTARTING = 8;
+ SERVICE_RESTARTING = 7;
// Process running a receiver.
- RECEIVER = 9;
+ RECEIVER = 8;
+ // Heavy-weight process (currently not used).
+ HEAVY_WEIGHT = 9;
// Process hosting home/launcher app when not on top.
HOME = 10;
// Process hosting the last app the user was in.
diff --git a/core/proto/android/util/common.proto b/core/proto/android/util/common.proto
index 308ef70..f8f7885 100644
--- a/core/proto/android/util/common.proto
+++ b/core/proto/android/util/common.proto
@@ -17,12 +17,15 @@
syntax = "proto2";
package android.util;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
option java_multiple_files = true;
/**
* Very basic data structure used by aggregated stats.
*/
message AggStats {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
optional int64 min = 1;
@@ -35,6 +38,7 @@
* Very basic data structure to represent Duration.
*/
message Duration {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
optional int64 start_ms = 1;
diff --git a/core/proto/android/util/log.proto b/core/proto/android/util/log.proto
index fd4fa9e..416c055 100644
--- a/core/proto/android/util/log.proto
+++ b/core/proto/android/util/log.proto
@@ -17,11 +17,15 @@
syntax = "proto2";
package android.util;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
option java_multiple_files = true;
// Represents a Text Log in logd
// Next Tag: 9
message TextLogEntry {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
optional uint64 sec = 1;
optional uint64 nanosec = 2;
@@ -47,6 +51,8 @@
// Represents a Binary Log in logd, need to look event-log-tags for more info.
// Next Tag: 8
message BinaryLogEntry {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
optional uint64 sec = 1;
optional uint64 nanosec = 2;
optional int32 uid = 3;
@@ -81,6 +87,8 @@
}
message LogProto {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
repeated TextLogEntry text_logs = 1;
repeated BinaryLogEntry binary_logs = 2;
diff --git a/core/proto/android/view/displayinfo.proto b/core/proto/android/view/displayinfo.proto
index 9ca4046..3ac8f3b 100644
--- a/core/proto/android/view/displayinfo.proto
+++ b/core/proto/android/view/displayinfo.proto
@@ -17,10 +17,14 @@
syntax = "proto2";
package android.view;
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+
option java_multiple_files = true;
/* represents DisplayInfo */
message DisplayInfoProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 logical_width = 1;
optional int32 logical_height = 2;
optional int32 app_width = 3;
diff --git a/core/proto/android/view/windowlayoutparams.proto b/core/proto/android/view/windowlayoutparams.proto
index f079e1e..0362ab1 100644
--- a/core/proto/android/view/windowlayoutparams.proto
+++ b/core/proto/android/view/windowlayoutparams.proto
@@ -18,12 +18,15 @@
import "frameworks/base/core/proto/android/graphics/pixelformat.proto";
import "frameworks/base/core/proto/android/view/display.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
package android.view;
option java_multiple_files = true;
/* represents WindowManager.LayoutParams */
message WindowLayoutParamsProto {
+ option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
optional int32 type = 1;
optional int32 x = 2;
optional int32 y = 3;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f1a9bd4..0861e710 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -971,6 +971,23 @@
android:description="@string/permdesc_manageOwnCalls"
android:protectionLevel="normal" />
+ <!-- Allows a calling app to continue a call which was started in another app. An example is a
+ video calling app that wants to continue a voice call on the user's mobile network.<p>
+ When the handover of a call from one app to another takes place, there are two devices
+ which are involved in the handover; the initiating and receiving devices. The initiating
+ device is where the request to handover the call was started, and the receiving device is
+ where the handover request is confirmed by the other party.<p>
+ This permission protects access to the
+ {@link android.telecom.TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)} which
+ the receiving side of the handover uses to accept a handover.
+ <p>Protection level: dangerous
+ -->
+ <permission android:name="android.permission.ACCEPT_HANDOVER"
+ android:permissionGroup="android.permission-group.PHONE"
+ android.label="@string/permlab_acceptHandover"
+ android:description="@string/permdesc_acceptHandovers"
+ android:protectionLevel="dangerous" />
+
<!-- ====================================================================== -->
<!-- Permissions for accessing the device microphone -->
<!-- ====================================================================== -->
@@ -3000,6 +3017,11 @@
<permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"
android:protectionLevel="signature|privileged|development" />
+ <!-- Allows an application to control the system's display brightness
+ @hide -->
+ <permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to control VPN.
<p>Not for use by third-party applications.</p>
@hide -->
diff --git a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
index 3254ebb..6f3dc8c 100644
--- a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
+++ b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
@@ -20,19 +20,54 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false" android:zAdjustment="top">
- <alpha android:fromAlpha="0" android:toAlpha="1.0"
- android:startOffset="300"
- android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
- android:interpolator="@interpolator/decelerate_quart"
- android:duration="167"/>
+ <alpha
+ android:fromAlpha="1"
+ android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="67"
+ android:duration="217"/>
- <translate android:fromYDelta="110%" android:toYDelta="0"
- android:startOffset="300"
- android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
- android:interpolator="@interpolator/decelerate_quint"
- android:duration="417"/>
+ <translate
+ android:fromXDelta="-105%"
+ android:toXDelta="0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/aggressive_ease"
+ android:startOffset="50"
+ android:duration="383"/>
- <!-- To keep the thumbnail around longer -->
+ <scale
+ android:fromXScale="1.0526"
+ android:toXScale="1"
+ android:fromYScale="1.0526"
+ android:toYScale="1"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_slow_in"
+ android:duration="283"/>
+
+ <scale
+ android:fromXScale="0.95"
+ android:toXScale="1"
+ android:fromYScale="0.95"
+ android:toYScale="1"
+ android:pivotX="50%"
+ android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_slow_in"
+ android:startOffset="283"
+ android:duration="317"/>
+
+ <!-- To keep the thumbnail around longer and fade out the thumbnail -->
<alpha android:fromAlpha="1.0" android:toAlpha="0"
android:fillEnabled="true" android:fillBefore="true" android:fillAfter="true"
android:interpolator="@interpolator/decelerate_quint"
diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml
index 2ee7cd8..0e66eda 100644
--- a/core/res/res/anim/task_open_enter.xml
+++ b/core/res/res/anim/task_open_enter.xml
@@ -14,7 +14,9 @@
** See the License for the specific language governing permissions and
** limitations under the License.
*/
---><!-- This should in sync with task_open_enter_cross_profile_apps.xml -->
+-->
+<!-- This should in sync with task_open_enter_cross_profile_apps.xml -->
+<!-- This should in sync with cross_profile_apps_thumbnail_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:zAdjustment="top">
diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml
index f8a77f8..ce4ac61 100644
--- a/core/res/res/values/colors_material.xml
+++ b/core/res/res/values/colors_material.xml
@@ -44,7 +44,8 @@
<color name="button_material_dark">#ff5a595b</color>
<color name="button_material_light">#ffd6d7d7</color>
- <color name="error_color_material">#F4511E</color>
+ <color name="error_color_material_dark">#ff7043</color><!-- deep orange 400 -->
+ <color name="error_color_material_light">#ff5722</color><!-- deep orange 500 -->
<color name="switch_thumb_normal_material_dark">#ffbdbdbd</color>
<color name="switch_thumb_normal_material_light">#fff1f1f1</color>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7e5a735..66e56bf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3256,8 +3256,8 @@
<dimen name="config_buttonCornerRadius">@dimen/control_corner_material</dimen>
<!-- Controls whether system buttons use all caps for text -->
<bool name="config_buttonTextAllCaps">true</bool>
- <!-- Name of the font family used for system buttons -->
- <string name="config_fontFamilyButton">@string/font_family_button_material</string>
+ <!-- Name of the font family used for system surfaces where the font should use medium weight -->
+ <string name="config_headlineFontFamilyMedium">@string/font_family_button_material</string>
<string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index b40117e..c90a0df 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -163,4 +163,9 @@
<!-- Action used to manually trigger an autofill request -->
<item type="id" name="autofill" />
+ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SHOW_TOOLTIP}. -->
+ <item type="id" name="accessibilityActionShowTooltip" />
+
+ <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_HIDE_TOOLTIP}. -->
+ <item type="id" name="accessibilityActionHideTooltip" />
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9cdf553..80fc5db 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2874,6 +2874,8 @@
</public-group>
<public-group type="id" first-id="0x01020044">
+ <public name="accessibilityActionShowTooltip" />
+ <public name="accessibilityActionHideTooltip" />
</public-group>
<public-group type="string" first-id="0x0104001b">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 71e963a..69d96fc 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1133,6 +1133,17 @@
<string name="permdesc_manageOwnCalls">Allows the app to route its calls through the system in
order to improve the calling experience.</string>
+ <!-- Title of an application permission. When granted the user is giving access to a third
+ party app to continue a call which originated in another app. For example, the user
+ could be in a voice call over their carrier's mobile network, and a third party video
+ calling app wants to continue that voice call as a video call. -->
+ <string name="permlab_acceptHandover">continue a call from another app</string>
+ <!-- Description of an application permission. When granted the user is giving access to a
+ third party app to continue a call which originated in another app. For example, the user
+ could be in a voice call over their carrier's mobile network, and a third party video
+ calling app wants to continue that voice call as a video call -->
+ <string name="permdesc_acceptHandovers">Allows the app to continue a call which was started in another app.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readPhoneNumbers">read phone numbers</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -2720,6 +2731,15 @@
<!-- Label for item in the text selection menu to trigger adding a contact [CHAR LIMIT=20] -->
<string name="add_contact">Add</string>
+ <!-- Label for item in the text selection menu to view the calendar for the selected time/date [CHAR LIMIT=20] -->
+ <string name="view_calendar">View</string>
+
+ <!-- Label for item in the text selection menu to create a calendar event at the selected time/date [CHAR LIMIT=20] -->
+ <string name="add_calendar_event">Schedule</string>
+
+ <!-- Label for item in the text selection menu to track a selected flight number [CHAR LIMIT=20] -->
+ <string name="view_flight">Track</string>
+
<!-- If the device is getting low on internal storage, a notification is shown to the user. This is the title of that notification. -->
<string name="low_internal_storage_view_title">Storage space running out</string>
<!-- If the device is getting low on internal storage, a notification is shown to the user. This is the message of that notification. -->
@@ -3027,6 +3047,8 @@
<!-- Notification title for a nearby open wireless network.-->
<string name="wifi_available_title">Connect to open Wi\u2011Fi network</string>
+ <!-- Notification title for a nearby carrier wireless network.-->
+ <string name="wifi_available_carrier_network_title">Connect to carrier Wi\u2011Fi network</string>
<!-- Notification title when the system is connecting to the specified open network. The network name is specified in the notification content. -->
<string name="wifi_available_title_connecting">Connecting to open Wi\u2011Fi network</string>
<!-- Notification title when the system has connected to the open network. The network name is specified in the notification content. -->
@@ -4621,6 +4643,11 @@
<!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
<string name="work_mode_turn_on">Turn on</string>
+ <!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
+ <string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
+ <!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] -->
+ <string name="deprecated_target_sdk_app_store">Check for update</string>
+
<!-- Notification title shown when new SMS/MMS is received while the device is locked [CHAR LIMIT=NONE] -->
<string name="new_sms_notification_title">You have new messages</string>
<!-- Notification content shown when new SMS/MMS is received while the device is locked [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index 189b3b7..1a51c1d 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -225,7 +225,7 @@
<style name="TextAppearance.DeviceDefault.SearchResult.Subtitle" parent="TextAppearance.Material.SearchResult.Subtitle"/>
<style name="TextAppearance.DeviceDefault.Widget" parent="TextAppearance.Material.Widget"/>
<style name="TextAppearance.DeviceDefault.Widget.Button" parent="TextAppearance.Material.Widget.Button">
- <item name="fontFamily">@string/config_fontFamilyButton</item>
+ <item name="fontFamily">@string/config_headlineFontFamilyMedium</item>
<item name="textAllCaps">@bool/config_buttonTextAllCaps</item>
</style>
<style name="TextAppearance.DeviceDefault.Widget.IconMenu.Item" parent="TextAppearance.Material.Widget.IconMenu.Item"/>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ee20873..710bbfb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -533,6 +533,9 @@
<java-symbol type="string" name="browse" />
<java-symbol type="string" name="sms" />
<java-symbol type="string" name="add_contact" />
+ <java-symbol type="string" name="view_calendar" />
+ <java-symbol type="string" name="add_calendar_event" />
+ <java-symbol type="string" name="view_flight" />
<java-symbol type="string" name="textSelectionCABTitle" />
<java-symbol type="string" name="BaMmi" />
<java-symbol type="string" name="CLIRDefaultOffNextCallOff" />
@@ -1912,6 +1915,7 @@
<java-symbol type="plurals" name="wifi_available" />
<java-symbol type="plurals" name="wifi_available_detailed" />
<java-symbol type="string" name="wifi_available_title" />
+ <java-symbol type="string" name="wifi_available_carrier_network_title" />
<java-symbol type="string" name="wifi_available_title_connecting" />
<java-symbol type="string" name="wifi_available_title_connected" />
<java-symbol type="string" name="wifi_available_title_failed_to_connect" />
@@ -2702,6 +2706,9 @@
<java-symbol type="string" name="work_mode_off_message" />
<java-symbol type="string" name="work_mode_turn_on" />
+ <java-symbol type="string" name="deprecated_target_sdk_message" />
+ <java-symbol type="string" name="deprecated_target_sdk_app_store" />
+
<!-- New SMS notification while phone is locked. -->
<java-symbol type="string" name="new_sms_notification_title" />
<java-symbol type="string" name="new_sms_notification_content" />
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 9e6b1ab..15d8fb7 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -51,7 +51,7 @@
<item name="primaryContentAlpha">@dimen/primary_content_alpha_material_dark</item>
<item name="secondaryContentAlpha">@dimen/secondary_content_alpha_material_dark</item>
<item name="backgroundDimAmount">0.6</item>
- <item name="colorError">@color/error_color_material</item>
+ <item name="colorError">@color/error_color_material_dark</item>
<!-- Text styles -->
<item name="textAppearance">@style/TextAppearance.Material</item>
@@ -420,6 +420,7 @@
<item name="primaryContentAlpha">@dimen/primary_content_alpha_material_light</item>
<item name="secondaryContentAlpha">@dimen/secondary_content_alpha_material_light</item>
<item name="backgroundDimAmount">0.6</item>
+ <item name="colorError">@color/error_color_material_light</item>
<!-- Text styles -->
<item name="textAppearance">@style/TextAppearance.Material</item>
@@ -811,6 +812,7 @@
<item name="colorBackground">@color/background_material_light</item>
<item name="colorBackgroundFloating">@color/background_floating_material_light</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
+ <item name="colorError">@color/error_color_material_light</item>
<item name="textColorPrimary">@color/text_color_primary</item>
<item name="textColorPrimaryInverse">@color/primary_text_material_dark</item>
@@ -844,6 +846,7 @@
<item name="colorBackground">@color/background_material_dark</item>
<item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
<item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
+ <item name="colorError">@color/error_color_material_dark</item>
<item name="textColorPrimary">@color/text_color_primary</item>
<item name="textColorPrimaryInverse">@color/primary_text_material_light</item>
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
index 896fd15..370659e 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/functional/RadioTunerTest.java
@@ -122,6 +122,7 @@
private void resetCallback() {
verify(mCallback, atLeast(0)).onMetadataChanged(any());
+ verify(mCallback, atLeast(0)).onProgramInfoChanged(any());
verifyNoMoreInteractions(mCallback);
Mockito.reset(mCallback);
}
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3e38010..53c22f6 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1045,6 +1045,13 @@
</intent-filter>
</activity>
+ <activity android:name="android.view.menu.ContextMenuActivity" android:label="ContextMenu" android:theme="@android:style/Theme.Material">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name="android.view.menu.MenuWith1Item" android:label="MenuWith1Item">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/core/tests/coretests/res/layout/context_menu.xml b/core/tests/coretests/res/layout/context_menu.xml
new file mode 100644
index 0000000..3b9e2bd
--- /dev/null
+++ b/core/tests/coretests/res/layout/context_menu.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2018, 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/context_menu_target_ltr"
+ android:orientation="horizontal"
+ android:layoutDirection="ltr"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="50px"
+ android:layout_marginEnd="50px">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="LTR"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/context_menu_target_rtl"
+ android:orientation="horizontal"
+ android:layoutDirection="rtl"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="50px"
+ android:layout_marginEnd="50px">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="RTL"/>
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/core/tests/coretests/src/android/os/BrightnessLimit.java b/core/tests/coretests/src/android/os/BrightnessLimit.java
index 43cd373..fabcf3d 100644
--- a/core/tests/coretests/src/android/os/BrightnessLimit.java
+++ b/core/tests/coretests/src/android/os/BrightnessLimit.java
@@ -16,12 +16,9 @@
package android.os;
-import android.os.IPowerManager;
-
import android.app.Activity;
+import android.hardware.display.DisplayManager;
import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
import android.provider.Settings;
import android.view.View;
import android.view.View.OnClickListener;
@@ -37,23 +34,16 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
+
setContentView(R.layout.brightness_limit);
-
+
Button b = findViewById(R.id.go);
b.setOnClickListener(this);
}
public void onClick(View v) {
- IPowerManager power = IPowerManager.Stub.asInterface(
- ServiceManager.getService("power"));
- if (power != null) {
- try {
- power.setTemporaryScreenBrightnessSettingOverride(0);
- } catch (RemoteException darn) {
-
- }
- }
+ DisplayManager dm = getSystemService(DisplayManager.class);
+ dm.setTemporaryBrightness(0);
Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, 0);
}
}
diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java
index 9893c16..0d250b8 100644
--- a/core/tests/coretests/src/android/os/PowerManagerTest.java
+++ b/core/tests/coretests/src/android/os/PowerManagerTest.java
@@ -21,9 +21,9 @@
import android.test.suitebuilder.annotation.SmallTest;
public class PowerManagerTest extends AndroidTestCase {
-
+
private PowerManager mPm;
-
+
/**
* Setup any common data for the upcoming tests.
*/
@@ -32,10 +32,10 @@
super.setUp();
mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
}
-
+
/**
* Confirm that the setup is good.
- *
+ *
* @throws Exception
*/
@SmallTest
@@ -45,7 +45,7 @@
/**
* Confirm that we can create functional wakelocks.
- *
+ *
* @throws Exception
*/
@SmallTest
@@ -61,22 +61,19 @@
wl = mPm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PARTIAL_WAKE_LOCK");
doTestWakeLock(wl);
-
- doTestSetBacklightBrightness();
- // TODO: Some sort of functional test (maybe not in the unit test here?)
+ // TODO: Some sort of functional test (maybe not in the unit test here?)
// that confirms that things are really happening e.g. screen power, keyboard power.
}
-
+
/**
* Confirm that we can't create dysfunctional wakelocks.
- *
+ *
* @throws Exception
*/
@SmallTest
public void testBadNewWakeLock() throws Exception {
-
- final int badFlags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK
+ final int badFlags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK
| PowerManager.SCREEN_DIM_WAKE_LOCK;
// wrap in try because we want the error here
try {
@@ -86,10 +83,10 @@
}
fail("Bad WakeLock flag was not caught.");
}
-
+
/**
* Apply a few tests to a wakelock to make sure it's healthy.
- *
+ *
* @param wl The wakelock to be tested.
*/
private void doTestWakeLock(PowerManager.WakeLock wl) {
@@ -98,7 +95,7 @@
assertTrue(wl.isHeld());
wl.release();
assertFalse(wl.isHeld());
-
+
// Try ref-counted acquire/release
wl.setReferenceCounted(true);
wl.acquire();
@@ -109,7 +106,7 @@
assertTrue(wl.isHeld());
wl.release();
assertFalse(wl.isHeld());
-
+
// Try non-ref-counted
wl.setReferenceCounted(false);
wl.acquire();
@@ -118,24 +115,7 @@
assertTrue(wl.isHeld());
wl.release();
assertFalse(wl.isHeld());
-
+
// TODO: Threaded test (needs handler) to make sure timed wakelocks work too
}
-
-
- /**
- * Test that calling {@link android.os.IHardwareService#setBacklights(int)} requires
- * permissions.
- * <p>Tests permission:
- * {@link android.Manifest.permission#DEVICE_POWER}
- */
- private void doTestSetBacklightBrightness() {
- try {
- mPm.setBacklightBrightness(0);
- fail("setBacklights did not throw SecurityException as expected");
- } catch (SecurityException e) {
- // expected
- }
- }
-
}
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index a427a2f..952a64d 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -349,6 +349,15 @@
assertEquals(100, wc.getAttributionUid());
}
+ public void testGetAttributionTag() {
+ WorkSource ws1 = new WorkSource();
+ WorkChain wc = ws1.createWorkChain();
+ wc.addNode(100, "tag");
+ assertEquals("tag", wc.getAttributionTag());
+ wc.addNode(200, "tag2");
+ assertEquals("tag", wc.getAttributionTag());
+ }
+
public void testRemove_fromChainedWorkSource() {
WorkSource ws1 = new WorkSource();
ws1.createWorkChain().addNode(50, "foo");
@@ -368,4 +377,25 @@
assertEquals(1, ws1.getWorkChains().size());
assertEquals(75, ws1.getWorkChains().get(0).getAttributionUid());
}
+
+ public void testTransferWorkChains() {
+ WorkSource ws1 = new WorkSource();
+ WorkChain wc1 = ws1.createWorkChain().addNode(100, "tag");
+ WorkChain wc2 = ws1.createWorkChain().addNode(200, "tag2");
+
+ WorkSource ws2 = new WorkSource();
+ ws2.transferWorkChains(ws1);
+
+ assertEquals(0, ws1.getWorkChains().size());
+ assertEquals(2, ws2.getWorkChains().size());
+ assertSame(wc1, ws2.getWorkChains().get(0));
+ assertSame(wc2, ws2.getWorkChains().get(1));
+
+ ws1.clear();
+ ws1.createWorkChain().addNode(300, "tag3");
+ ws1.transferWorkChains(ws2);
+ assertEquals(0, ws2.getWorkChains().size());
+ assertSame(wc1, ws1.getWorkChains().get(0));
+ assertSame(wc2, ws1.getWorkChains().get(1));
+ }
}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 08d023d..2f747ec 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -44,6 +44,12 @@
public class SettingsBackupTest {
/**
+ * see {@link com.google.android.systemui.power.EnhancedEstimatesGoogleImpl} for more details
+ */
+ public static final String HYBRID_SYSUI_BATTERY_WARNING_FLAGS =
+ "hybrid_sysui_battery_warning_flags";
+
+ /**
* The following blacklists contain settings that should *not* be backed up and restored to
* another device. As a general rule, anything that is not user configurable should be
* blacklisted (and conversely, things that *are* user configurable *should* be backed up)
@@ -114,6 +120,12 @@
Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
Settings.Global.BATTERY_STATS_CONSTANTS,
Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
+ Settings.Global.BLE_SCAN_LOW_POWER_WINDOW_MS,
+ Settings.Global.BLE_SCAN_LOW_POWER_INTERVAL_MS,
+ Settings.Global.BLE_SCAN_BALANCED_WINDOW_MS,
+ Settings.Global.BLE_SCAN_BALANCED_INTERVAL_MS,
+ Settings.Global.BLE_SCAN_LOW_LATENCY_WINDOW_MS,
+ Settings.Global.BLE_SCAN_LOW_LATENCY_INTERVAL_MS,
Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
Settings.Global.BLUETOOTH_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX,
@@ -226,6 +238,7 @@
Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
Settings.Global.HTTP_PROXY,
+ HYBRID_SYSUI_BATTERY_WARNING_FLAGS,
Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY,
Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY,
Settings.Global.INSTANT_APP_DEXOPT_ENABLED,
@@ -431,7 +444,8 @@
Settings.Global.ZEN_MODE_CONFIG_ETAG,
Settings.Global.ZEN_MODE_RINGER_LEVEL,
Settings.Global.ZRAM_ENABLED,
- Settings.Global.OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION);
+ Settings.Global.OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION,
+ Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED);
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
@@ -483,6 +497,7 @@
Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY,
Settings.Secure.INSTALL_NON_MARKET_APPS,
Settings.Secure.LAST_SETUP_SHOWN,
+ Settings.Secure.LOCATION_CHANGER,
Settings.Secure.LOCATION_MODE,
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, // Candidate?
Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate?
diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index f6300ee..6f1d47d 100644
--- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -132,7 +132,7 @@
public void buildForStaticLayout() {
MeasuredParagraph mt = null;
- mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, false, null);
+ mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, false, false, null);
assertNotNull(mt);
assertNotNull(mt.getChars());
assertEquals("XXX", charsToString(mt.getChars()));
@@ -147,7 +147,7 @@
// Recycle it
MeasuredParagraph mt2 =
- MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, false, mt);
+ MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, false, false, mt);
assertEquals(mt2, mt);
assertNotNull(mt2.getChars());
assertEquals("VVV", charsToString(mt.getChars()));
diff --git a/core/tests/coretests/src/android/util/PollingCheck.java b/core/tests/coretests/src/android/util/PollingCheck.java
new file mode 100644
index 0000000..468b9b2
--- /dev/null
+++ b/core/tests/coretests/src/android/util/PollingCheck.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 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.util;
+
+import org.junit.Assert;
+
+/**
+ * Utility used for testing that allows to poll for a certain condition to happen within a timeout.
+ *
+ * Code copied from com.android.compatibility.common.util.PollingCheck
+ */
+public abstract class PollingCheck {
+
+ private static final long DEFAULT_TIMEOUT = 3000;
+ private static final long TIME_SLICE = 50;
+ private final long mTimeout;
+
+ /**
+ * The condition that the PollingCheck should use to proceed successfully.
+ */
+ public interface PollingCheckCondition {
+
+ /**
+ * @return Whether the polling condition has been met.
+ */
+ boolean canProceed();
+ }
+
+ public PollingCheck(long timeout) {
+ mTimeout = timeout;
+ }
+
+ protected abstract boolean check();
+
+ /**
+ * Start running the polling check.
+ */
+ public void run() {
+ if (check()) {
+ return;
+ }
+
+ long timeout = mTimeout;
+ while (timeout > 0) {
+ try {
+ Thread.sleep(TIME_SLICE);
+ } catch (InterruptedException e) {
+ Assert.fail("unexpected InterruptedException");
+ }
+
+ if (check()) {
+ return;
+ }
+
+ timeout -= TIME_SLICE;
+ }
+
+ Assert.fail("unexpected timeout");
+ }
+
+ /**
+ * Instantiate and start polling for a given condition with a default 3000ms timeout.
+ *
+ * @param condition The condition to check for success.
+ */
+ public static void waitFor(final PollingCheckCondition condition) {
+ new PollingCheck(DEFAULT_TIMEOUT) {
+ @Override
+ protected boolean check() {
+ return condition.canProceed();
+ }
+ }.run();
+ }
+
+ /**
+ * Instantiate and start polling for a given condition.
+ *
+ * @param timeout Time out in ms
+ * @param condition The condition to check for success.
+ */
+ public static void waitFor(long timeout, final PollingCheckCondition condition) {
+ new PollingCheck(timeout) {
+ @Override
+ protected boolean check() {
+ return condition.canProceed();
+ }
+ }.run();
+ }
+}
+
diff --git a/core/tests/coretests/src/android/view/menu/ContextMenuActivity.java b/core/tests/coretests/src/android/view/menu/ContextMenuActivity.java
new file mode 100644
index 0000000..830b3d5
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/ContextMenuActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 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.view.menu;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View;
+
+import com.android.frameworks.coretests.R;
+
+public class ContextMenuActivity extends Activity {
+
+ static final String LABEL_ITEM = "Item";
+ static final String LABEL_SUBMENU = "Submenu";
+ static final String LABEL_SUBITEM = "Subitem";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.context_menu);
+ registerForContextMenu(getTargetLtr());
+ registerForContextMenu(getTargetRtl());
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ menu.add(LABEL_ITEM);
+ menu.addSubMenu(LABEL_SUBMENU).add(LABEL_SUBITEM);
+ }
+
+ View getTargetLtr() {
+ return findViewById(R.id.context_menu_target_ltr);
+ }
+
+ View getTargetRtl() {
+ return findViewById(R.id.context_menu_target_rtl);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/menu/ContextMenuTest.java b/core/tests/coretests/src/android/view/menu/ContextMenuTest.java
new file mode 100644
index 0000000..59d4e55
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/ContextMenuTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2018 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.view.menu;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.support.test.filters.MediumTest;
+import android.test.ActivityInstrumentationTestCase;
+import android.util.PollingCheck;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.espresso.ContextMenuUtils;
+
+@MediumTest
+public class ContextMenuTest extends ActivityInstrumentationTestCase<ContextMenuActivity> {
+
+ public ContextMenuTest() {
+ super("com.android.frameworks.coretests", ContextMenuActivity.class);
+ }
+
+ public void testContextMenuPositionLtr() throws InterruptedException {
+ testMenuPosition(getActivity().getTargetLtr());
+ }
+
+ public void testContextMenuPositionRtl() throws InterruptedException {
+ testMenuPosition(getActivity().getTargetRtl());
+ }
+
+ private void testMenuPosition(View target) throws InterruptedException {
+ final int minScreenDimension = getMinScreenDimension();
+ if (minScreenDimension < 320) {
+ // Assume there is insufficient room for the context menu to be aligned properly.
+ return;
+ }
+
+ int offsetX = target.getWidth() / 2;
+ int offsetY = target.getHeight() / 2;
+
+ getInstrumentation().runOnMainSync(() -> target.performLongClick(offsetX, offsetY));
+
+ PollingCheck.waitFor(
+ () -> ContextMenuUtils.isMenuItemClickable(ContextMenuActivity.LABEL_SUBMENU));
+
+ ContextMenuUtils.assertContextMenuAlignment(target, offsetX, offsetY);
+
+ ContextMenuUtils.clickMenuItem(ContextMenuActivity.LABEL_SUBMENU);
+
+ PollingCheck.waitFor(
+ () -> ContextMenuUtils.isMenuItemClickable(ContextMenuActivity.LABEL_SUBITEM));
+
+ if (minScreenDimension < getCascadingMenuTreshold()) {
+ // A non-cascading submenu should be displayed at the same location as its parent.
+ // Not testing cascading submenu position, as it is positioned differently.
+ ContextMenuUtils.assertContextMenuAlignment(target, offsetX, offsetY);
+ }
+ }
+
+ /**
+ * Returns the minimum of the default display's width and height.
+ */
+ private int getMinScreenDimension() {
+ final WindowManager windowManager = (WindowManager) getActivity().getSystemService(
+ Context.WINDOW_SERVICE);
+ final Display display = windowManager.getDefaultDisplay();
+ final Point displaySize = new Point();
+ display.getRealSize(displaySize);
+ return Math.min(displaySize.x, displaySize.y);
+ }
+
+ /**
+ * Returns the minimum display size where cascading submenus are supported.
+ */
+ private int getCascadingMenuTreshold() {
+ // Use the same dimension resource as in MenuPopupHelper.createPopup().
+ return getActivity().getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.cascading_menus_min_smallest_width);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
index 9ee7fac..8a81743 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationTest.java
@@ -32,7 +32,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Calendar;
import java.util.Locale;
+import java.util.TimeZone;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -146,8 +148,12 @@
@Test
public void testParcelOptions() {
+ Calendar referenceTime = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.US);
+ referenceTime.setTimeInMillis(946771200000L); // 2000-01-02
+
TextClassification.Options reference = new TextClassification.Options();
reference.setDefaultLocales(new LocaleList(Locale.US, Locale.GERMANY));
+ reference.setReferenceTime(referenceTime);
// Parcel and unparcel.
final Parcel parcel = Parcel.obtain();
@@ -157,5 +163,6 @@
parcel);
assertEquals("en-US,de-DE", result.getDefaultLocales().toLanguageTags());
+ assertEquals(referenceTime, result.getReferenceTime());
}
}
diff --git a/core/tests/coretests/src/android/widget/espresso/ContextMenuUtils.java b/core/tests/coretests/src/android/widget/espresso/ContextMenuUtils.java
index c8218aa..02ee9be 100644
--- a/core/tests/coretests/src/android/widget/espresso/ContextMenuUtils.java
+++ b/core/tests/coretests/src/android/widget/espresso/ContextMenuUtils.java
@@ -17,25 +17,33 @@
package android.widget.espresso;
import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
import static android.support.test.espresso.matcher.ViewMatchers.hasFocus;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
+
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.not;
-import com.android.internal.view.menu.ListMenuItemView;
-
import android.support.test.espresso.NoMatchingRootException;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.ViewInteraction;
import android.support.test.espresso.matcher.ViewMatchers;
+import android.view.View;
import android.widget.MenuPopupWindow.MenuDropDownListView;
+import com.android.internal.view.menu.ListMenuItemView;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
/**
* Espresso utility methods for the context menu.
*/
@@ -82,10 +90,15 @@
private static void asssertContextMenuContainsItemWithEnabledState(String itemLabel,
boolean enabled) {
onContextMenu().check(matches(
- hasDescendant(allOf(
- isAssignableFrom(ListMenuItemView.class),
- enabled ? isEnabled() : not(isEnabled()),
- hasDescendant(withText(itemLabel))))));
+ hasDescendant(getVisibleMenuItemMatcher(itemLabel, enabled))));
+ }
+
+ private static Matcher<View> getVisibleMenuItemMatcher(String itemLabel, boolean enabled) {
+ return allOf(
+ isAssignableFrom(ListMenuItemView.class),
+ hasDescendant(withText(itemLabel)),
+ enabled ? isEnabled() : not(isEnabled()),
+ isDisplayingAtLeast(90));
}
/**
@@ -107,4 +120,70 @@
public static void assertContextMenuContainsItemDisabled(String itemLabel) {
asssertContextMenuContainsItemWithEnabledState(itemLabel, false);
}
+
+ /**
+ * Asserts that the context menu window is aligned to a given view with a given offset.
+ *
+ * @param anchor Anchor view.
+ * @param offsetX x offset
+ * @param offsetY y offset.
+ * @throws AssertionError if the assertion fails
+ */
+ public static void assertContextMenuAlignment(View anchor, int offsetX, int offsetY) {
+ int [] expectedLocation = new int[2];
+ anchor.getLocationOnScreen(expectedLocation);
+ expectedLocation[0] += offsetX;
+ expectedLocation[1] += offsetY;
+
+ final boolean rtl = anchor.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+
+ onContextMenu().check(matches(new TypeSafeMatcher<View>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("root view ");
+ description.appendText(rtl ? "right" : "left");
+ description.appendText("=");
+ description.appendText(Integer.toString(offsetX));
+ description.appendText(", top=");
+ description.appendText(Integer.toString(offsetY));
+ }
+
+ @Override
+ public boolean matchesSafely(View view) {
+ View rootView = view.getRootView();
+ int [] actualLocation = new int[2];
+ rootView.getLocationOnScreen(actualLocation);
+ if (rtl) {
+ actualLocation[0] += rootView.getWidth();
+ }
+ return expectedLocation[0] == actualLocation[0]
+ && expectedLocation[1] == actualLocation[1];
+ }
+ }));
+ }
+
+ /**
+ * Check is the menu item is clickable (i.e. visible and enabled).
+ *
+ * @param itemLabel Label of the item.
+ * @return True if the menu item is clickable.
+ */
+ public static boolean isMenuItemClickable(String itemLabel) {
+ try {
+ onContextMenu().check(matches(
+ hasDescendant(getVisibleMenuItemMatcher(itemLabel, true))));
+ return true;
+ } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
+ return false;
+ }
+ }
+
+ /**
+ * Click on a menu item with the specified label
+ * @param itemLabel Label of the item.
+ */
+ public static void clickMenuItem(String itemLabel) {
+ onView(getVisibleMenuItemMatcher(itemLabel, true))
+ .inRoot(withDecorView(hasFocus())).perform(click());
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index a55563a..82ac9da 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -23,10 +23,12 @@
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.BatteryStats.HistoryItem;
+import android.os.BatteryStats.Uid.Sensor;
import android.os.WorkSource;
import android.support.test.filters.SmallTest;
import android.view.Display;
+import com.android.internal.os.BatteryStatsImpl.DualTimer;
import com.android.internal.os.BatteryStatsImpl.Uid;
import junit.framework.TestCase;
@@ -446,4 +448,52 @@
pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate");
assertEquals(0, pkg.getWakeupAlarmStats().size());
}
+
+ @SmallTest
+ public void testNoteGpsChanged() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+ bi.mForceOnBattery = true;
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ WorkSource ws = new WorkSource();
+ ws.add(UID);
+
+ bi.noteGpsChangedLocked(new WorkSource(), ws);
+ DualTimer t = bi.getUidStatsLocked(UID).getSensorTimerLocked(Sensor.GPS, false);
+ assertNotNull(t);
+ assertTrue(t.isRunningLocked());
+
+ bi.noteGpsChangedLocked(ws, new WorkSource());
+ t = bi.getUidStatsLocked(UID).getSensorTimerLocked(Sensor.GPS, false);
+ assertFalse(t.isRunningLocked());
+ }
+
+ @SmallTest
+ public void testNoteGpsChanged_workSource() {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+ bi.setRecordAllHistoryLocked(true);
+ bi.forceRecordAllHistory();
+ bi.mForceOnBattery = true;
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+ WorkSource ws = new WorkSource();
+ ws.createWorkChain().addNode(UID, "com.foo");
+
+ bi.noteGpsChangedLocked(new WorkSource(), ws);
+ DualTimer t = bi.getUidStatsLocked(UID).getSensorTimerLocked(Sensor.GPS, false);
+ assertNotNull(t);
+ assertTrue(t.isRunningLocked());
+
+ bi.noteGpsChangedLocked(ws, new WorkSource());
+ t = bi.getUidStatsLocked(UID).getSensorTimerLocked(Sensor.GPS, false);
+ assertFalse(t.isRunningLocked());
+ }
}
diff --git a/core/tests/overlaytests/device/AndroidManifest.xml b/core/tests/overlaytests/device/AndroidManifest.xml
new file mode 100644
index 0000000..e01caee
--- /dev/null
+++ b/core/tests/overlaytests/device/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.om.test">
+
+ <uses-sdk android:minSdkVersion="21" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.om.test" />
+
+</manifest>
diff --git a/core/tests/overlaytests/OverlayAppFiltered/Android.mk b/core/tests/overlaytests/device/OverlayAppFiltered/Android.mk
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFiltered/Android.mk
rename to core/tests/overlaytests/device/OverlayAppFiltered/Android.mk
diff --git a/core/tests/overlaytests/OverlayAppFiltered/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppFiltered/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFiltered/AndroidManifest.xml
rename to core/tests/overlaytests/device/OverlayAppFiltered/AndroidManifest.xml
diff --git a/core/tests/overlaytests/OverlayAppFiltered/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayAppFiltered/res/raw/lorem_ipsum.txt
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFiltered/res/raw/lorem_ipsum.txt
rename to core/tests/overlaytests/device/OverlayAppFiltered/res/raw/lorem_ipsum.txt
diff --git a/core/tests/overlaytests/OverlayAppFiltered/res/values/config.xml b/core/tests/overlaytests/device/OverlayAppFiltered/res/values/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFiltered/res/values/config.xml
rename to core/tests/overlaytests/device/OverlayAppFiltered/res/values/config.xml
diff --git a/core/tests/overlaytests/OverlayAppFiltered/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayAppFiltered/res/xml/integer.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFiltered/res/xml/integer.xml
rename to core/tests/overlaytests/device/OverlayAppFiltered/res/xml/integer.xml
diff --git a/core/tests/overlaytests/OverlayAppFirst/Android.mk b/core/tests/overlaytests/device/OverlayAppFirst/Android.mk
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/Android.mk
rename to core/tests/overlaytests/device/OverlayAppFirst/Android.mk
diff --git a/core/tests/overlaytests/OverlayAppFirst/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppFirst/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/AndroidManifest.xml
rename to core/tests/overlaytests/device/OverlayAppFirst/AndroidManifest.xml
diff --git a/core/tests/overlaytests/OverlayAppFirst/res/drawable-nodpi/drawable.jpg b/core/tests/overlaytests/device/OverlayAppFirst/res/drawable-nodpi/drawable.jpg
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/res/drawable-nodpi/drawable.jpg
rename to core/tests/overlaytests/device/OverlayAppFirst/res/drawable-nodpi/drawable.jpg
Binary files differ
diff --git a/core/tests/overlaytests/OverlayAppFirst/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayAppFirst/res/raw/lorem_ipsum.txt
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/res/raw/lorem_ipsum.txt
rename to core/tests/overlaytests/device/OverlayAppFirst/res/raw/lorem_ipsum.txt
diff --git a/core/tests/overlaytests/OverlayAppFirst/res/values-sv/config.xml b/core/tests/overlaytests/device/OverlayAppFirst/res/values-sv/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/res/values-sv/config.xml
rename to core/tests/overlaytests/device/OverlayAppFirst/res/values-sv/config.xml
diff --git a/core/tests/overlaytests/OverlayAppFirst/res/values/config.xml b/core/tests/overlaytests/device/OverlayAppFirst/res/values/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/res/values/config.xml
rename to core/tests/overlaytests/device/OverlayAppFirst/res/values/config.xml
diff --git a/core/tests/overlaytests/OverlayAppFirst/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayAppFirst/res/xml/integer.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/res/xml/integer.xml
rename to core/tests/overlaytests/device/OverlayAppFirst/res/xml/integer.xml
diff --git a/core/tests/overlaytests/OverlayAppSecond/Android.mk b/core/tests/overlaytests/device/OverlayAppSecond/Android.mk
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/Android.mk
rename to core/tests/overlaytests/device/OverlayAppSecond/Android.mk
diff --git a/core/tests/overlaytests/OverlayAppSecond/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppSecond/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/AndroidManifest.xml
rename to core/tests/overlaytests/device/OverlayAppSecond/AndroidManifest.xml
diff --git a/core/tests/overlaytests/OverlayAppSecond/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayAppSecond/res/raw/lorem_ipsum.txt
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/res/raw/lorem_ipsum.txt
rename to core/tests/overlaytests/device/OverlayAppSecond/res/raw/lorem_ipsum.txt
diff --git a/core/tests/overlaytests/OverlayAppSecond/res/values-sv/config.xml b/core/tests/overlaytests/device/OverlayAppSecond/res/values-sv/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/res/values-sv/config.xml
rename to core/tests/overlaytests/device/OverlayAppSecond/res/values-sv/config.xml
diff --git a/core/tests/overlaytests/OverlayAppSecond/res/values/config.xml b/core/tests/overlaytests/device/OverlayAppSecond/res/values/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/res/values/config.xml
rename to core/tests/overlaytests/device/OverlayAppSecond/res/values/config.xml
diff --git a/core/tests/overlaytests/OverlayAppSecond/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayAppSecond/res/xml/integer.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/res/xml/integer.xml
rename to core/tests/overlaytests/device/OverlayAppSecond/res/xml/integer.xml
diff --git a/core/tests/overlaytests/OverlayTest/Android.mk b/core/tests/overlaytests/device/OverlayTest/Android.mk
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/Android.mk
rename to core/tests/overlaytests/device/OverlayTest/Android.mk
diff --git a/core/tests/overlaytests/OverlayTest/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayTest/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/AndroidManifest.xml
rename to core/tests/overlaytests/device/OverlayTest/AndroidManifest.xml
diff --git a/core/tests/overlaytests/OverlayTest/res/drawable-nodpi/drawable.jpg b/core/tests/overlaytests/device/OverlayTest/res/drawable-nodpi/drawable.jpg
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/res/drawable-nodpi/drawable.jpg
rename to core/tests/overlaytests/device/OverlayTest/res/drawable-nodpi/drawable.jpg
Binary files differ
diff --git a/core/tests/overlaytests/OverlayTest/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayTest/res/raw/lorem_ipsum.txt
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/res/raw/lorem_ipsum.txt
rename to core/tests/overlaytests/device/OverlayTest/res/raw/lorem_ipsum.txt
diff --git a/core/tests/overlaytests/OverlayTest/res/values-sv/config.xml b/core/tests/overlaytests/device/OverlayTest/res/values-sv/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/res/values-sv/config.xml
rename to core/tests/overlaytests/device/OverlayTest/res/values-sv/config.xml
diff --git a/core/tests/overlaytests/OverlayTest/res/values/config.xml b/core/tests/overlaytests/device/OverlayTest/res/values/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/res/values/config.xml
rename to core/tests/overlaytests/device/OverlayTest/res/values/config.xml
diff --git a/core/tests/overlaytests/OverlayTest/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayTest/res/xml/integer.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/res/xml/integer.xml
rename to core/tests/overlaytests/device/OverlayTest/res/xml/integer.xml
diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java
rename to core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java
diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java
rename to core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java
diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java
rename to core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java
diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java
rename to core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java
diff --git a/core/tests/overlaytests/OverlayTestOverlay/Android.mk b/core/tests/overlaytests/device/OverlayTestOverlay/Android.mk
similarity index 100%
rename from core/tests/overlaytests/OverlayTestOverlay/Android.mk
rename to core/tests/overlaytests/device/OverlayTestOverlay/Android.mk
diff --git a/core/tests/overlaytests/OverlayTestOverlay/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayTestOverlay/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTestOverlay/AndroidManifest.xml
rename to core/tests/overlaytests/device/OverlayTestOverlay/AndroidManifest.xml
diff --git a/core/tests/overlaytests/OverlayTestOverlay/res/values/config.xml b/core/tests/overlaytests/device/OverlayTestOverlay/res/values/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTestOverlay/res/values/config.xml
rename to core/tests/overlaytests/device/OverlayTestOverlay/res/values/config.xml
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
new file mode 100644
index 0000000..d8e1fc1
--- /dev/null
+++ b/core/tests/overlaytests/host/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := OverlayHostTests
+LOCAL_JAVA_LIBRARIES := tradefed
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_TARGET_REQUIRED_MODULES := \
+ OverlayHostTests_BadSignatureOverlay \
+ OverlayHostTests_PlatformSignatureStaticOverlay \
+ OverlayHostTests_PlatformSignatureOverlay \
+ OverlayHostTests_PlatformSignatureOverlayV2
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Include to build test-apps.
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/core/tests/overlaytests/host/AndroidTest.xml b/core/tests/overlaytests/host/AndroidTest.xml
new file mode 100644
index 0000000..6884623
--- /dev/null
+++ b/core/tests/overlaytests/host/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<configuration description="Host-driven test module config for OverlayHostTests">
+ <option name="test-tag" value="OverlayHostTests" />
+ <option name="test-suite-tag" value="apct" />
+
+ <test class="com.android.tradefed.testtype.HostTest">
+ <option name="class" value="com.android.server.om.hosttest.InstallOverlayTests" />
+ </test>
+</configuration>
diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
new file mode 100644
index 0000000..5093710
--- /dev/null
+++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package com.android.server.om.hosttest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class InstallOverlayTests extends BaseHostJUnit4Test {
+
+ private static final String OVERLAY_PACKAGE_NAME =
+ "com.android.server.om.hosttest.signature_overlay";
+
+ @Test
+ public void failToInstallNonPlatformSignedOverlay() throws Exception {
+ try {
+ installPackage("OverlayHostTests_BadSignatureOverlay.apk");
+ fail("installed a non-platform signed overlay");
+ } catch (Exception e) {
+ // Expected.
+ }
+ assertFalse(overlayManagerContainsPackage());
+ }
+
+ @Test
+ public void failToInstallPlatformSignedStaticOverlay() throws Exception {
+ try {
+ installPackage("OverlayHostTests_PlatformSignatureStaticOverlay.apk");
+ fail("installed a static overlay");
+ } catch (Exception e) {
+ // Expected.
+ }
+ assertFalse(overlayManagerContainsPackage());
+ }
+
+ @Test
+ public void succeedToInstallPlatformSignedOverlay() throws Exception {
+ installPackage("OverlayHostTests_PlatformSignatureOverlay.apk");
+ assertTrue(overlayManagerContainsPackage());
+ }
+
+ @Test
+ public void succeedToInstallPlatformSignedOverlayAndUpdate() throws Exception {
+ installPackage("OverlayHostTests_PlatformSignatureOverlay.apk");
+ assertTrue(overlayManagerContainsPackage());
+ assertEquals("v1", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName());
+
+ installPackage("OverlayHostTests_PlatformSignatureOverlayV2.apk");
+ assertTrue(overlayManagerContainsPackage());
+ assertEquals("v2", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName());
+ }
+
+ private boolean overlayManagerContainsPackage() throws Exception {
+ return getDevice().executeShellCommand("cmd overlay list")
+ .contains(OVERLAY_PACKAGE_NAME);
+ }
+}
diff --git a/core/tests/overlaytests/host/test-apps/Android.mk b/core/tests/overlaytests/host/test-apps/Android.mk
new file mode 100644
index 0000000..5c7187e
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2018 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.
+
+include $(call all-subdir-makefiles)
+
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
new file mode 100644
index 0000000..b051a82
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
@@ -0,0 +1,52 @@
+# Copyright (C) 2018 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+my_package_prefix := com.android.server.om.hosttest.signature_overlay
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_BadSignatureOverlay
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
+LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlayV2
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
+LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
+include $(BUILD_PACKAGE)
+
+my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..2d68439
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.om.hosttest.signature_overlay">
+ <overlay android:targetPackage="android" />
+</manifest>
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/src/dummy b/core/tests/overlaytests/host/test-apps/SignatureOverlay/src/dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/src/dummy
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/static/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/SignatureOverlay/static/AndroidManifest.xml
new file mode 100644
index 0000000..139dd96
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/static/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.om.hosttest.signature_overlay">
+ <overlay android:targetPackage="android" android:isStatic="true" />
+</manifest>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0949a90..6c8aaf0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -350,6 +350,7 @@
<permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
<permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
<permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+ <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
<permission name="android.permission.CONTROL_VPN"/>
<permission name="android.permission.DUMP"/>
<permission name="android.permission.GET_APP_OPS_STATS"/>
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 2d8c717..627d551 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -22,6 +22,8 @@
import android.annotation.Size;
import android.graphics.Canvas.VertexMode;
import android.text.GraphicsOperations;
+import android.text.MeasuredParagraph;
+import android.text.MeasuredText;
import android.text.SpannableString;
import android.text.SpannedString;
import android.text.TextUtils;
@@ -453,7 +455,8 @@
throwIfHasHwBitmapInSwMode(paint);
nDrawTextRun(mNativeCanvasWrapper, text, index, count, contextIndex, contextCount,
- x, y, isRtl, paint.getNativeInstance());
+ x, y, isRtl, paint.getNativeInstance(), 0 /* measured text */,
+ 0 /* measured text offset */);
}
public void drawTextRun(@NonNull CharSequence text, int start, int end, int contextStart,
@@ -483,8 +486,20 @@
int len = end - start;
char[] buf = TemporaryBuffer.obtain(contextLen);
TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
+ long measuredTextPtr = 0;
+ int measuredTextOffset = 0;
+ if (text instanceof MeasuredText) {
+ MeasuredText mt = (MeasuredText) text;
+ int paraIndex = mt.findParaIndex(start);
+ if (end <= mt.getParagraphEnd(paraIndex)) {
+ // Only suppor the same paragraph.
+ measuredTextPtr = mt.getMeasuredParagraph(paraIndex).getNativePtr();
+ measuredTextOffset = start - mt.getParagraphStart(paraIndex);
+ }
+ }
nDrawTextRun(mNativeCanvasWrapper, buf, start - contextStart, len,
- 0, contextLen, x, y, isRtl, paint.getNativeInstance());
+ 0, contextLen, x, y, isRtl, paint.getNativeInstance(),
+ measuredTextPtr, measuredTextOffset);
TemporaryBuffer.recycle(buf);
}
}
@@ -623,7 +638,8 @@
int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint);
private static native void nDrawTextRun(long nativeCanvas, char[] text, int start, int count,
- int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint);
+ int contextStart, int contextCount, float x, float y, boolean isRtl, long nativePaint,
+ long nativeMeasuredText, int measuredTextOffset);
private static native void nDrawTextOnPath(long nativeCanvas, char[] text, int index, int count,
long nativePath, float hOffset, float vOffset, int bidiFlags, long nativePaint);
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 02d22be..3de050b 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -920,10 +920,6 @@
Resources res = src.getResources();
byte[] np = bm.getNinePatchChunk();
if (np != null && NinePatch.isNinePatchChunk(np)) {
- if (res != null) {
- bm.setDensity(res.getDisplayMetrics().densityDpi);
- }
-
Rect opticalInsets = new Rect();
bm.getOpticalInsets(opticalInsets);
Rect padding = new Rect();
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 6d3ddd5..3034a10 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -166,8 +166,7 @@
@Override
public void start() {
- if (isRunning() == false) {
- nStart(mNativePtr);
+ if (nStart(mNativePtr)) {
invalidateSelf();
}
}
@@ -186,7 +185,7 @@
private static native int nGetAlpha(long nativePtr);
private static native void nSetColorFilter(long nativePtr, long nativeFilter);
private static native boolean nIsRunning(long nativePtr);
- private static native void nStart(long nativePtr);
+ private static native boolean nStart(long nativePtr);
private static native void nStop(long nativePtr);
private static native long nNativeByteSize(long nativePtr);
}
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index e3740e3..7ad062a 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -163,7 +163,7 @@
/**
* Create a drawable by opening a given file path and decoding the bitmap.
*/
- @SuppressWarnings("unused")
+ @SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" })
public BitmapDrawable(Resources res, String filepath) {
this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
mBitmapState.mTargetDensity = mTargetDensity;
@@ -188,7 +188,7 @@
/**
* Create a drawable by decoding a bitmap from the given input stream.
*/
- @SuppressWarnings("unused")
+ @SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" })
public BitmapDrawable(Resources res, java.io.InputStream is) {
this(new BitmapState(BitmapFactory.decodeStream(is)), null);
mBitmapState.mTargetDensity = mTargetDensity;
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 6c3aea2..f5a6f49 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -42,6 +42,7 @@
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
+import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -814,6 +815,16 @@
}
}
+ /**
+ * @param mode to draw this drawable with
+ * @hide
+ */
+ @Override
+ public void setXfermode(@Nullable Xfermode mode) {
+ super.setXfermode(mode);
+ mFillPaint.setXfermode(mode);
+ }
+
private void buildPathIfDirty() {
final GradientState st = mGradientState;
if (mPathIsDirty) {
diff --git a/graphics/java/android/graphics/drawable/RippleBackground.java b/graphics/java/android/graphics/drawable/RippleBackground.java
index 4571553..41d3698 100644
--- a/graphics/java/android/graphics/drawable/RippleBackground.java
+++ b/graphics/java/android/graphics/drawable/RippleBackground.java
@@ -63,15 +63,21 @@
}
}
- public void setState(boolean focused, boolean hovered, boolean animateChanged) {
+ public void setState(boolean focused, boolean hovered, boolean pressed) {
+ if (!mFocused) {
+ focused = focused && !pressed;
+ }
+ if (!mHovered) {
+ hovered = hovered && !pressed;
+ }
if (mHovered != hovered || mFocused != focused) {
mHovered = hovered;
mFocused = focused;
- onStateChanged(animateChanged);
+ onStateChanged();
}
}
- private void onStateChanged(boolean animateChanged) {
+ private void onStateChanged() {
float newOpacity = 0.0f;
if (mHovered) newOpacity += .25f;
if (mFocused) newOpacity += .75f;
@@ -79,14 +85,10 @@
mAnimator.cancel();
mAnimator = null;
}
- if (animateChanged) {
- mAnimator = ObjectAnimator.ofFloat(this, OPACITY, newOpacity);
- mAnimator.setDuration(OPACITY_DURATION);
- mAnimator.setInterpolator(LINEAR_INTERPOLATOR);
- mAnimator.start();
- } else {
- mOpacity = newOpacity;
- }
+ mAnimator = ObjectAnimator.ofFloat(this, OPACITY, newOpacity);
+ mAnimator.setDuration(OPACITY_DURATION);
+ mAnimator.setInterpolator(LINEAR_INTERPOLATOR);
+ mAnimator.start();
}
public void jumpToFinal() {
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index b883656..0da61c2 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -264,7 +264,7 @@
}
setRippleActive(enabled && pressed);
- setBackgroundActive(hovered, focused);
+ setBackgroundActive(hovered, focused, pressed);
return changed;
}
@@ -280,13 +280,13 @@
}
}
- private void setBackgroundActive(boolean hovered, boolean focused) {
+ private void setBackgroundActive(boolean hovered, boolean focused, boolean pressed) {
if (mBackground == null && (hovered || focused)) {
mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
mBackground.setup(mState.mMaxRadius, mDensity);
}
if (mBackground != null) {
- mBackground.setState(focused, hovered, true);
+ mBackground.setState(focused, hovered, pressed);
}
}
@@ -878,7 +878,10 @@
// Grab the color for the current state and cut the alpha channel in
// half so that the ripple and background together yield full alpha.
- final int color = mState.mColor.getColorForState(getState(), Color.BLACK);
+ int color = mState.mColor.getColorForState(getState(), Color.BLACK);
+ if (Color.alpha(color) > 128) {
+ color = (color & 0x00FFFFFF) | 0x80000000;
+ }
final Paint p = mRipplePaint;
if (mMaskColorFilter != null) {
diff --git a/graphics/java/android/graphics/drawable/RippleForeground.java b/graphics/java/android/graphics/drawable/RippleForeground.java
index ecbf578..4129868 100644
--- a/graphics/java/android/graphics/drawable/RippleForeground.java
+++ b/graphics/java/android/graphics/drawable/RippleForeground.java
@@ -289,6 +289,7 @@
opacity.setInterpolator(LINEAR_INTERPOLATOR);
opacity.addListener(mAnimationListener);
opacity.setStartDelay(computeFadeOutDelay());
+ opacity.setStartValue(mOwner.getRipplePaint().getAlpha());
mPendingHwAnimators.add(opacity);
invalidateSelf();
}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index e25386b..ded427e 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -38,6 +38,7 @@
import android.security.keystore.KeyExpiredException;
import android.security.keystore.KeyNotYetValidException;
import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.StrongBoxUnavailableException;
import android.security.keystore.UserNotAuthenticatedException;
import android.util.Log;
@@ -65,6 +66,7 @@
public static final int VALUE_CORRUPTED = 8;
public static final int UNDEFINED_ACTION = 9;
public static final int WRONG_PASSWORD = 10;
+ public static final int HARDWARE_TYPE_UNAVAILABLE = -68;
/**
* Per operation authentication is needed before this operation is valid.
@@ -123,7 +125,6 @@
*/
public static final int FLAG_STRONGBOX = 1 << 4;
-
// States
public enum State { UNLOCKED, LOCKED, UNINITIALIZED };
@@ -730,6 +731,58 @@
}
}
+ // Keep in sync with confirmationui/1.0/types.hal.
+ public static final int CONFIRMATIONUI_OK = 0;
+ public static final int CONFIRMATIONUI_CANCELED = 1;
+ public static final int CONFIRMATIONUI_ABORTED = 2;
+ public static final int CONFIRMATIONUI_OPERATION_PENDING = 3;
+ public static final int CONFIRMATIONUI_IGNORED = 4;
+ public static final int CONFIRMATIONUI_SYSTEM_ERROR = 5;
+ public static final int CONFIRMATIONUI_UNIMPLEMENTED = 6;
+ public static final int CONFIRMATIONUI_UNEXPECTED = 7;
+ public static final int CONFIRMATIONUI_UIERROR = 0x10000;
+ public static final int CONFIRMATIONUI_UIERROR_MISSING_GLYPH = 0x10001;
+ public static final int CONFIRMATIONUI_UIERROR_MESSAGE_TOO_LONG = 0x10002;
+ public static final int CONFIRMATIONUI_UIERROR_MALFORMED_UTF8_ENCODING = 0x10003;
+
+ /**
+ * Requests keystore call into the confirmationui HAL to display a prompt.
+ *
+ * @param listener the binder to use for callbacks.
+ * @param promptText the prompt to display.
+ * @param extraData extra data / nonce from application.
+ * @param locale the locale as a BCP 47 langauge tag.
+ * @param uiOptionsAsFlags the UI options to use, as flags.
+ * @return one of the {@code CONFIRMATIONUI_*} constants, for
+ * example {@code KeyStore.CONFIRMATIONUI_OK}.
+ */
+ public int presentConfirmationPrompt(IBinder listener, String promptText, byte[] extraData,
+ String locale, int uiOptionsAsFlags) {
+ try {
+ return mBinder.presentConfirmationPrompt(listener, promptText, extraData, locale,
+ uiOptionsAsFlags);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return CONFIRMATIONUI_SYSTEM_ERROR;
+ }
+ }
+
+ /**
+ * Requests keystore call into the confirmationui HAL to cancel displaying a prompt.
+ *
+ * @param listener the binder passed to the {@link #presentConfirmationPrompt} method.
+ * @return one of the {@code CONFIRMATIONUI_*} constants, for
+ * example {@code KeyStore.CONFIRMATIONUI_OK}.
+ */
+ public int cancelConfirmationPrompt(IBinder listener) {
+ try {
+ return mBinder.cancelConfirmationPrompt(listener);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Cannot connect to keystore", e);
+ return CONFIRMATIONUI_SYSTEM_ERROR;
+ }
+ }
+
/**
* Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error
* code.
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 379e177..f721ed3 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -290,6 +290,9 @@
spec.isUserAuthenticationValidWhileOnBody(),
spec.isInvalidatedByBiometricEnrollment(),
GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */);
+ if (spec.isTrustedUserPresenceRequired()) {
+ args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED);
+ }
KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
args,
mKeymasterAlgorithm,
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index dba3949..d1eb688 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -21,6 +21,7 @@
import android.security.GateKeeper;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
+import android.security.KeyStoreException;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterCertificateChain;
@@ -451,7 +452,7 @@
throw new IllegalStateException("Not initialized");
}
- final int flags = (mEncryptionAtRestRequired) ? KeyStore.FLAG_ENCRYPTED : 0;
+ int flags = (mEncryptionAtRestRequired) ? KeyStore.FLAG_ENCRYPTED : 0;
if (((flags & KeyStore.FLAG_ENCRYPTED) != 0)
&& (mKeyStore.state() != KeyStore.State.UNLOCKED)) {
throw new IllegalStateException(
@@ -459,6 +460,10 @@
+ ", but the user has not yet entered the credential");
}
+ if (mSpec.isStrongBoxBacked()) {
+ flags |= KeyStore.FLAG_STRONGBOX;
+ }
+
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
mRng, (mKeySizeBits + 7) / 8);
@@ -501,8 +506,12 @@
int errorCode = mKeyStore.generateKey(privateKeyAlias, args, additionalEntropy,
mEntryUid, flags, resultingKeyCharacteristics);
if (errorCode != KeyStore.NO_ERROR) {
- throw new ProviderException(
- "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
+ if (errorCode == KeyStore.HARDWARE_TYPE_UNAVAILABLE) {
+ throw new StrongBoxUnavailableException("Failed to generate key pair");
+ } else {
+ throw new ProviderException(
+ "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
+ }
}
}
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index fdb885db..9df37f5 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -177,6 +177,9 @@
&& (keymasterSwEnforcedUserAuthenticators == 0);
boolean userAuthenticationValidWhileOnBody =
keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY);
+ boolean trustedUserPresenceRequred =
+ keyCharacteristics.hwEnforced.getBoolean(
+ KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED);
boolean invalidatedByBiometricEnrollment = false;
if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_FINGERPRINT
@@ -203,6 +206,7 @@
(int) userAuthenticationValidityDurationSeconds,
userAuthenticationRequirementEnforcedBySecureHardware,
userAuthenticationValidWhileOnBody,
+ trustedUserPresenceRequred,
invalidatedByBiometricEnrollment);
}
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 1e2b873..a896c72 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -258,6 +258,7 @@
private final boolean mRandomizedEncryptionRequired;
private final boolean mUserAuthenticationRequired;
private final int mUserAuthenticationValidityDurationSeconds;
+ private final boolean mTrustedUserPresenceRequred;
private final byte[] mAttestationChallenge;
private final boolean mUniqueIdIncluded;
private final boolean mUserAuthenticationValidWhileOnBody;
@@ -287,6 +288,7 @@
boolean randomizedEncryptionRequired,
boolean userAuthenticationRequired,
int userAuthenticationValidityDurationSeconds,
+ boolean trustedUserPresenceRequired,
byte[] attestationChallenge,
boolean uniqueIdIncluded,
boolean userAuthenticationValidWhileOnBody,
@@ -332,6 +334,7 @@
mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
mRandomizedEncryptionRequired = randomizedEncryptionRequired;
mUserAuthenticationRequired = userAuthenticationRequired;
+ mTrustedUserPresenceRequred = trustedUserPresenceRequired;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
mUniqueIdIncluded = uniqueIdIncluded;
@@ -562,6 +565,14 @@
}
/**
+ * Returns {@code true} if the key is authorized to be used only if a test of user presence has
+ * been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls.
+ */
+ public boolean isTrustedUserPresenceRequired() {
+ return mTrustedUserPresenceRequred;
+ }
+
+ /**
* Returns the attestation challenge value that will be placed in attestation certificate for
* this key pair.
*
@@ -658,6 +669,7 @@
private boolean mRandomizedEncryptionRequired = true;
private boolean mUserAuthenticationRequired;
private int mUserAuthenticationValidityDurationSeconds = -1;
+ private boolean mTrustedUserPresenceRequired = false;
private byte[] mAttestationChallenge = null;
private boolean mUniqueIdIncluded = false;
private boolean mUserAuthenticationValidWhileOnBody;
@@ -718,6 +730,7 @@
mUserAuthenticationRequired = sourceSpec.isUserAuthenticationRequired();
mUserAuthenticationValidityDurationSeconds =
sourceSpec.getUserAuthenticationValidityDurationSeconds();
+ mTrustedUserPresenceRequired = sourceSpec.isTrustedUserPresenceRequired();
mAttestationChallenge = sourceSpec.getAttestationChallenge();
mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded();
mUserAuthenticationValidWhileOnBody = sourceSpec.isUserAuthenticationValidWhileOnBody();
@@ -1095,6 +1108,16 @@
}
/**
+ * Sets whether a test of user presence is required to be performed between the
+ * {@code Signature.initSign()} and {@code Signature.sign()} method calls.
+ */
+ @NonNull
+ public Builder setTrustedUserPresenceRequired(boolean required) {
+ mTrustedUserPresenceRequired = required;
+ return this;
+ }
+
+ /**
* Sets whether an attestation certificate will be generated for this key pair, and what
* challenge value will be placed in the certificate. The attestation certificate chain
* can be retrieved with with {@link java.security.KeyStore#getCertificateChain(String)}.
@@ -1221,6 +1244,7 @@
mRandomizedEncryptionRequired,
mUserAuthenticationRequired,
mUserAuthenticationValidityDurationSeconds,
+ mTrustedUserPresenceRequired,
mAttestationChallenge,
mUniqueIdIncluded,
mUserAuthenticationValidWhileOnBody,
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index f553319..864f62a 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -80,6 +80,7 @@
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware;
private final boolean mUserAuthenticationValidWhileOnBody;
+ private final boolean mTrustedUserPresenceRequired;
private final boolean mInvalidatedByBiometricEnrollment;
/**
@@ -101,6 +102,7 @@
int userAuthenticationValidityDurationSeconds,
boolean userAuthenticationRequirementEnforcedBySecureHardware,
boolean userAuthenticationValidWhileOnBody,
+ boolean trustedUserPresenceRequired,
boolean invalidatedByBiometricEnrollment) {
mKeystoreAlias = keystoreKeyAlias;
mInsideSecureHardware = insideSecureHardware;
@@ -121,6 +123,7 @@
mUserAuthenticationRequirementEnforcedBySecureHardware =
userAuthenticationRequirementEnforcedBySecureHardware;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
+ mTrustedUserPresenceRequired = trustedUserPresenceRequired;
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
}
@@ -301,4 +304,12 @@
public boolean isInvalidatedByBiometricEnrollment() {
return mInvalidatedByBiometricEnrollment;
}
+
+ /**
+ * Returns {@code true} if the key can only be only be used if a test for user presence has
+ * succeeded since Signature.initSign() has been called.
+ */
+ public boolean isTrustedUserPresenceRequired() {
+ return mTrustedUserPresenceRequired;
+ }
}
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 7cb8e37..e5fdea7 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -101,6 +101,7 @@
out.writeBoolean(mSpec.isUniqueIdIncluded());
out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody());
out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment());
+ out.writeBoolean(mSpec.isTrustedUserPresenceRequired());
}
private static Date readDateOrNull(Parcel in) {
@@ -164,6 +165,7 @@
builder.setUniqueIdIncluded(in.readBoolean());
builder.setUserAuthenticationValidWhileOnBody(in.readBoolean());
builder.setInvalidatedByBiometricEnrollment(in.readBoolean());
+ builder.setTrustedUserPresenceRequired(in.readBoolean());
mSpec = builder.build();
}
diff --git a/keystore/java/android/security/keystore/StrongBoxUnavailableException.java b/keystore/java/android/security/keystore/StrongBoxUnavailableException.java
index ad41a58..66a77ed 100644
--- a/keystore/java/android/security/keystore/StrongBoxUnavailableException.java
+++ b/keystore/java/android/security/keystore/StrongBoxUnavailableException.java
@@ -16,6 +16,9 @@
package android.security.keystore;
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+
import java.security.ProviderException;
/**
@@ -24,5 +27,13 @@
*/
public class StrongBoxUnavailableException extends ProviderException {
+ /**
+ * @hide
+ */
+ public StrongBoxUnavailableException(String message) {
+ super(message,
+ new KeyStoreException(KeyStore.HARDWARE_TYPE_UNAVAILABLE, "No StrongBox available")
+ );
+ }
}
diff --git a/keystore/java/android/security/keystore/UserPresenceUnavailableException.java b/keystore/java/android/security/keystore/UserPresenceUnavailableException.java
new file mode 100644
index 0000000..cf4099e
--- /dev/null
+++ b/keystore/java/android/security/keystore/UserPresenceUnavailableException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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.security.keystore;
+
+import java.security.InvalidAlgorithmParameterException;
+
+/**
+ * Indicates the condition that a proof of user-presence was
+ * requested but this proof was not presented.
+ */
+public class UserPresenceUnavailableException extends InvalidAlgorithmParameterException {
+ /**
+ * Constructs a {@code UserPresenceUnavailableException} without a detail message or cause.
+ */
+ public UserPresenceUnavailableException() {
+ super("No Strong Box available.");
+ }
+
+ /**
+ * Constructs a {@code UserPresenceUnavailableException} using the provided detail message
+ * but no cause.
+ */
+ public UserPresenceUnavailableException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a {@code UserPresenceUnavailableException} using the provided detail message
+ * and cause.
+ */
+ public UserPresenceUnavailableException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 251b2e7..70d5216 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -145,6 +145,7 @@
"tests/TypeWrappers_test.cpp",
"tests/ZipUtils_test.cpp",
],
+ static_libs: ["libgmock"],
target: {
android: {
srcs: [
@@ -171,6 +172,7 @@
// Actual benchmarks.
"tests/AssetManager2_bench.cpp",
+ "tests/AttributeResolution_bench.cpp",
"tests/SparseEntry_bench.cpp",
"tests/Theme_bench.cpp",
],
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 415d3e3..a558ff7 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -36,6 +36,31 @@
namespace android {
+struct FindEntryResult {
+ // A pointer to the resource table entry for this resource.
+ // If the size of the entry is > sizeof(ResTable_entry), it can be cast to
+ // a ResTable_map_entry and processed as a bag/map.
+ const ResTable_entry* entry;
+
+ // The configuration for which the resulting entry was defined. This is already swapped to host
+ // endianness.
+ ResTable_config config;
+
+ // The bitmask of configuration axis with which the resource value varies.
+ uint32_t type_flags;
+
+ // The dynamic package ID map for the package from which this resource came from.
+ const DynamicRefTable* dynamic_ref_table;
+
+ // The string pool reference to the type's name. This uses a different string pool than
+ // the global string pool, but this is hidden from the caller.
+ StringPoolRef type_string_ref;
+
+ // The string pool reference to the entry's name. This uses a different string pool than
+ // the global string pool, but this is hidden from the caller.
+ StringPoolRef entry_string_ref;
+};
+
AssetManager2::AssetManager2() {
memset(&configuration_, 0, sizeof(configuration_));
}
@@ -44,6 +69,7 @@
bool invalidate_caches) {
apk_assets_ = apk_assets;
BuildDynamicRefTable();
+ RebuildFilterList();
if (invalidate_caches) {
InvalidateCaches(static_cast<uint32_t>(-1));
}
@@ -79,7 +105,7 @@
PackageGroup* package_group = &package_groups_[idx];
// Add the package and to the set of packages with the same ID.
- package_group->packages_.push_back(package.get());
+ package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));
// Add the package name -> build time ID mappings.
@@ -94,7 +120,7 @@
// Now assign the runtime IDs so that we have a build-time to runtime ID map.
const auto package_groups_end = package_groups_.end();
for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
- const std::string& package_name = iter->packages_[0]->GetPackageName();
+ const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()),
iter->dynamic_ref_table.mAssignedPackageId);
@@ -108,17 +134,20 @@
std::string list;
for (size_t i = 0; i < package_ids_.size(); i++) {
if (package_ids_[i] != 0xff) {
- base::StringAppendF(&list, "%02x -> %d, ", (int) i, package_ids_[i]);
+ base::StringAppendF(&list, "%02x -> %d, ", (int)i, package_ids_[i]);
}
}
LOG(INFO) << "Package ID map: " << list;
- for (const auto& package_group: package_groups_) {
- list = "";
- for (const auto& package : package_group.packages_) {
- base::StringAppendF(&list, "%s(%02x), ", package->GetPackageName().c_str(), package->GetPackageId());
- }
- LOG(INFO) << base::StringPrintf("PG (%02x): ", package_group.dynamic_ref_table.mAssignedPackageId) << list;
+ for (const auto& package_group : package_groups_) {
+ list = "";
+ for (const auto& package : package_group.packages_) {
+ base::StringAppendF(&list, "%s(%02x), ", package.loaded_package_->GetPackageName().c_str(),
+ package.loaded_package_->GetPackageId());
+ }
+ LOG(INFO) << base::StringPrintf("PG (%02x): ",
+ package_group.dynamic_ref_table.mAssignedPackageId)
+ << list;
}
}
@@ -157,52 +186,54 @@
configuration_ = configuration;
if (diff) {
+ RebuildFilterList();
InvalidateCaches(static_cast<uint32_t>(diff));
}
}
std::set<ResTable_config> AssetManager2::GetResourceConfigurations(bool exclude_system,
- bool exclude_mipmap) {
+ bool exclude_mipmap) const {
ATRACE_CALL();
std::set<ResTable_config> configurations;
for (const PackageGroup& package_group : package_groups_) {
- for (const LoadedPackage* package : package_group.packages_) {
- if (exclude_system && package->IsSystem()) {
+ for (const ConfiguredPackage& package : package_group.packages_) {
+ if (exclude_system && package.loaded_package_->IsSystem()) {
continue;
}
- package->CollectConfigurations(exclude_mipmap, &configurations);
+ package.loaded_package_->CollectConfigurations(exclude_mipmap, &configurations);
}
}
return configurations;
}
std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system,
- bool merge_equivalent_languages) {
+ bool merge_equivalent_languages) const {
ATRACE_CALL();
std::set<std::string> locales;
for (const PackageGroup& package_group : package_groups_) {
- for (const LoadedPackage* package : package_group.packages_) {
- if (exclude_system && package->IsSystem()) {
+ for (const ConfiguredPackage& package : package_group.packages_) {
+ if (exclude_system && package.loaded_package_->IsSystem()) {
continue;
}
- package->CollectLocales(merge_equivalent_languages, &locales);
+ package.loaded_package_->CollectLocales(merge_equivalent_languages, &locales);
}
}
return locales;
}
-std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, Asset::AccessMode mode) {
+std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename,
+ Asset::AccessMode mode) const {
const std::string new_path = "assets/" + filename;
return OpenNonAsset(new_path, mode);
}
std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie,
- Asset::AccessMode mode) {
+ Asset::AccessMode mode) const {
const std::string new_path = "assets/" + filename;
return OpenNonAsset(new_path, cookie, mode);
}
-std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) {
+std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) const {
ATRACE_CALL();
std::string full_path = "assets/" + dirname;
@@ -236,7 +267,7 @@
// is inconsistent for split APKs.
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
Asset::AccessMode mode,
- ApkAssetsCookie* out_cookie) {
+ ApkAssetsCookie* out_cookie) const {
ATRACE_CALL();
for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {
std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
@@ -255,7 +286,8 @@
}
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
- ApkAssetsCookie cookie, Asset::AccessMode mode) {
+ ApkAssetsCookie cookie,
+ Asset::AccessMode mode) const {
ATRACE_CALL();
if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
return {};
@@ -264,14 +296,13 @@
}
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
- bool stop_at_first_match, FindEntryResult* out_entry) {
- ATRACE_CALL();
-
+ bool /*stop_at_first_match*/,
+ FindEntryResult* out_entry) const {
// Might use this if density_override != 0.
ResTable_config density_override_config;
// Select our configuration or generate a density override configuration.
- ResTable_config* desired_config = &configuration_;
+ const ResTable_config* desired_config = &configuration_;
if (density_override != 0 && density_override != configuration_.density) {
density_override_config = configuration_;
density_override_config.density = density_override;
@@ -285,53 +316,135 @@
const uint32_t package_id = get_package_id(resid);
const uint8_t type_idx = get_type_id(resid) - 1;
- const uint16_t entry_id = get_entry_id(resid);
+ const uint16_t entry_idx = get_entry_id(resid);
- const uint8_t idx = package_ids_[package_id];
- if (idx == 0xff) {
+ const uint8_t package_idx = package_ids_[package_id];
+ if (package_idx == 0xff) {
LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid);
return kInvalidCookie;
}
- FindEntryResult best_entry;
- ApkAssetsCookie best_cookie = kInvalidCookie;
- uint32_t cumulated_flags = 0u;
-
- const PackageGroup& package_group = package_groups_[idx];
+ const PackageGroup& package_group = package_groups_[package_idx];
const size_t package_count = package_group.packages_.size();
- FindEntryResult current_entry;
- for (size_t i = 0; i < package_count; i++) {
- const LoadedPackage* loaded_package = package_group.packages_[i];
- if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, ¤t_entry)) {
+
+ ApkAssetsCookie best_cookie = kInvalidCookie;
+ const LoadedPackage* best_package = nullptr;
+ const ResTable_type* best_type = nullptr;
+ const ResTable_config* best_config = nullptr;
+ ResTable_config best_config_copy;
+ uint32_t best_offset = 0u;
+ uint32_t type_flags = 0u;
+
+ // If desired_config is the same as the set configuration, then we can use our filtered list
+ // and we don't need to match the configurations, since they already matched.
+ const bool use_fast_path = desired_config == &configuration_;
+
+ for (size_t pi = 0; pi < package_count; pi++) {
+ const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
+ const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_;
+ ApkAssetsCookie cookie = package_group.cookies_[pi];
+
+ // If the type IDs are offset in this package, we need to take that into account when searching
+ // for a type.
+ const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx);
+ if (UNLIKELY(type_spec == nullptr)) {
continue;
}
- cumulated_flags |= current_entry.type_flags;
+ uint16_t local_entry_idx = entry_idx;
- const ResTable_config* current_config = current_entry.config;
- const ResTable_config* best_config = best_entry.config;
- if (best_cookie == kInvalidCookie ||
- current_config->isBetterThan(*best_config, desired_config) ||
- (loaded_package->IsOverlay() && current_config->compare(*best_config) == 0)) {
- best_entry = current_entry;
- best_cookie = package_group.cookies_[i];
- if (stop_at_first_match) {
- break;
+ // If there is an IDMAP supplied with this package, translate the entry ID.
+ if (type_spec->idmap_entries != nullptr) {
+ if (!LoadedIdmap::Lookup(type_spec->idmap_entries, local_entry_idx, &local_entry_idx)) {
+ // There is no mapping, so the resource is not meant to be in this overlay package.
+ continue;
+ }
+ }
+
+ type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx);
+
+ // If the package is an overlay, then even configurations that are the same MUST be chosen.
+ const bool package_is_overlay = loaded_package->IsOverlay();
+
+ const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
+ if (use_fast_path) {
+ const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations;
+ const size_t type_count = candidate_configs.size();
+ for (uint32_t i = 0; i < type_count; i++) {
+ const ResTable_config& this_config = candidate_configs[i];
+
+ // We can skip calling ResTable_config::match() because we know that all candidate
+ // configurations that do NOT match have been filtered-out.
+ if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
+ (package_is_overlay && this_config.compare(*best_config) == 0)) {
+ // The configuration matches and is better than the previous selection.
+ // Find the entry value if it exists for this configuration.
+ const ResTable_type* type_chunk = filtered_group.types[i];
+ const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx);
+ if (offset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
+
+ best_cookie = cookie;
+ best_package = loaded_package;
+ best_type = type_chunk;
+ best_config = &this_config;
+ best_offset = offset;
+ }
+ }
+ } else {
+ // This is the slower path, which doesn't use the filtered list of configurations.
+ // Here we must read the ResTable_config from the mmapped APK, convert it to host endianness
+ // and fill in any new fields that did not exist when the APK was compiled.
+ // Furthermore when selecting configurations we can't just record the pointer to the
+ // ResTable_config, we must copy it.
+ const auto iter_end = type_spec->types + type_spec->type_count;
+ for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+ ResTable_config this_config;
+ this_config.copyFromDtoH((*iter)->config);
+
+ if (this_config.match(*desired_config)) {
+ if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
+ (package_is_overlay && this_config.compare(*best_config) == 0)) {
+ // The configuration matches and is better than the previous selection.
+ // Find the entry value if it exists for this configuration.
+ const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx);
+ if (offset == ResTable_type::NO_ENTRY) {
+ continue;
+ }
+
+ best_cookie = cookie;
+ best_package = loaded_package;
+ best_type = *iter;
+ best_config_copy = this_config;
+ best_config = &best_config_copy;
+ best_offset = offset;
+ }
+ }
}
}
}
- if (best_cookie == kInvalidCookie) {
+ if (UNLIKELY(best_cookie == kInvalidCookie)) {
return kInvalidCookie;
}
- *out_entry = best_entry;
+ const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
+ if (UNLIKELY(best_entry == nullptr)) {
+ return kInvalidCookie;
+ }
+
+ out_entry->entry = best_entry;
+ out_entry->config = *best_config;
+ out_entry->type_flags = type_flags;
+ out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1);
+ out_entry->entry_string_ref =
+ StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index);
out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
- out_entry->type_flags = cumulated_flags;
return best_cookie;
}
-bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
+bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const {
ATRACE_CALL();
FindEntryResult entry;
@@ -341,7 +454,8 @@
return false;
}
- const LoadedPackage* package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageForId(resid);
+ const LoadedPackage* package =
+ apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
if (package == nullptr) {
return false;
}
@@ -369,7 +483,7 @@
return true;
}
-bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) {
+bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) const {
FindEntryResult entry;
ApkAssetsCookie cookie =
FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
@@ -383,7 +497,7 @@
ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
uint16_t density_override, Res_value* out_value,
ResTable_config* out_selected_config,
- uint32_t* out_flags) {
+ uint32_t* out_flags) const {
ATRACE_CALL();
FindEntryResult entry;
@@ -402,7 +516,7 @@
// Create a reference since we can't represent this complex type as a Res_value.
out_value->dataType = Res_value::TYPE_REFERENCE;
out_value->data = resid;
- *out_selected_config = *entry.config;
+ *out_selected_config = entry.config;
*out_flags = entry.type_flags;
return cookie;
}
@@ -414,7 +528,7 @@
// Convert the package ID to the runtime assigned package ID.
entry.dynamic_ref_table->lookupResourceValue(out_value);
- *out_selected_config = *entry.config;
+ *out_selected_config = entry.config;
*out_flags = entry.type_flags;
return cookie;
}
@@ -422,16 +536,14 @@
ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
ResTable_config* in_out_selected_config,
uint32_t* in_out_flags,
- uint32_t* out_last_reference) {
+ uint32_t* out_last_reference) const {
ATRACE_CALL();
constexpr const int kMaxIterations = 20;
for (size_t iteration = 0u; in_out_value->dataType == Res_value::TYPE_REFERENCE &&
in_out_value->data != 0u && iteration < kMaxIterations;
iteration++) {
- if (out_last_reference != nullptr) {
- *out_last_reference = in_out_value->data;
- }
+ *out_last_reference = in_out_value->data;
uint32_t new_flags = 0u;
cookie = GetResource(in_out_value->data, true /*may_be_bag*/, 0u /*density_override*/,
in_out_value, in_out_selected_config, &new_flags);
@@ -492,7 +604,8 @@
// Attributes, arrays, etc don't have a resource id as the name. They specify
// other data, which would be wrong to change via a lookup.
if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) {
- LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid);
+ LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key,
+ resid);
return nullptr;
}
}
@@ -524,7 +637,8 @@
const ResolvedBag* parent_bag = GetBag(parent_resid);
if (parent_bag == nullptr) {
// Failed to get the parent that should exist.
- LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, resid);
+ LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid,
+ resid);
return nullptr;
}
@@ -543,7 +657,8 @@
uint32_t child_key = dtohl(map_entry->name.ident);
if (!is_internal_resid(child_key)) {
if (entry.dynamic_ref_table->lookupResourceId(&child_key) != NO_ERROR) {
- LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, resid);
+ LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key,
+ resid);
return nullptr;
}
}
@@ -582,7 +697,8 @@
uint32_t new_key = dtohl(map_entry->name.ident);
if (!is_internal_resid(new_key)) {
if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) {
- LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid);
+ LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key,
+ resid);
return nullptr;
}
}
@@ -638,7 +754,7 @@
uint32_t AssetManager2::GetResourceId(const std::string& resource_name,
const std::string& fallback_type,
- const std::string& fallback_package) {
+ const std::string& fallback_package) const {
StringPiece package_name, type, entry;
if (!ExtractResourceName(resource_name, &package_name, &type, &entry)) {
return 0u;
@@ -670,7 +786,8 @@
const static std::u16string kAttrPrivate16 = u"^attr-private";
for (const PackageGroup& package_group : package_groups_) {
- for (const LoadedPackage* package : package_group.packages_) {
+ for (const ConfiguredPackage& package_impl : package_group.packages_) {
+ const LoadedPackage* package = package_impl.loaded_package_;
if (package_name != package->GetPackageName()) {
// All packages in the same group are expected to have the same package name.
break;
@@ -692,6 +809,32 @@
return 0u;
}
+void AssetManager2::RebuildFilterList() {
+ for (PackageGroup& group : package_groups_) {
+ for (ConfiguredPackage& impl : group.packages_) {
+ // Destroy it.
+ impl.filtered_configs_.~ByteBucketArray();
+
+ // Re-create it.
+ new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
+
+ // Create the filters here.
+ impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) {
+ FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index);
+ const auto iter_end = spec->types + spec->type_count;
+ for (auto iter = spec->types; iter != iter_end; ++iter) {
+ ResTable_config this_config;
+ this_config.copyFromDtoH((*iter)->config);
+ if (this_config.match(configuration_)) {
+ group.configurations.push_back(this_config);
+ group.types.push_back(*iter);
+ }
+ }
+ });
+ }
+ }
+}
+
void AssetManager2::InvalidateCaches(uint32_t diff) {
if (diff == 0xffffffffu) {
// Everything must go.
@@ -872,7 +1015,7 @@
ApkAssetsCookie Theme::ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
ResTable_config* in_out_selected_config,
uint32_t* in_out_type_spec_flags,
- uint32_t* out_last_ref) {
+ uint32_t* out_last_ref) const {
if (in_out_value->dataType == Res_value::TYPE_ATTRIBUTE) {
uint32_t new_flags;
cookie = GetAttribute(in_out_value->data, in_out_value, &new_flags);
diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp
index 60e3845..f912af4 100644
--- a/libs/androidfw/AttributeResolution.cpp
+++ b/libs/androidfw/AttributeResolution.cpp
@@ -20,13 +20,18 @@
#include <log/log.h>
+#include "androidfw/AssetManager2.h"
#include "androidfw/AttributeFinder.h"
-#include "androidfw/ResourceTypes.h"
constexpr bool kDebugStyles = false;
namespace android {
+// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0.
+static uint32_t ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) {
+ return cookie != kInvalidCookie ? static_cast<uint32_t>(cookie + 1) : static_cast<uint32_t>(-1);
+}
+
class XmlAttributeFinder
: public BackTrackingAttributeFinder<XmlAttributeFinder, size_t> {
public:
@@ -44,58 +49,53 @@
};
class BagAttributeFinder
- : public BackTrackingAttributeFinder<BagAttributeFinder, const ResTable::bag_entry*> {
+ : public BackTrackingAttributeFinder<BagAttributeFinder, const ResolvedBag::Entry*> {
public:
- BagAttributeFinder(const ResTable::bag_entry* start,
- const ResTable::bag_entry* end)
- : BackTrackingAttributeFinder(start, end) {}
+ BagAttributeFinder(const ResolvedBag* bag)
+ : BackTrackingAttributeFinder(bag != nullptr ? bag->entries : nullptr,
+ bag != nullptr ? bag->entries + bag->entry_count : nullptr) {
+ }
- inline uint32_t GetAttribute(const ResTable::bag_entry* entry) const {
- return entry->map.name.ident;
+ inline uint32_t GetAttribute(const ResolvedBag::Entry* entry) const {
+ return entry->key;
}
};
-bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr,
- uint32_t def_style_res, uint32_t* src_values,
- size_t src_values_length, uint32_t* attrs,
- size_t attrs_length, uint32_t* out_values,
- uint32_t* out_indices) {
+bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res,
+ uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
+ size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
if (kDebugStyles) {
ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme,
def_style_attr, def_style_res);
}
- const ResTable& res = theme->getResTable();
+ AssetManager2* assetmanager = theme->GetAssetManager();
ResTable_config config;
Res_value value;
int indices_idx = 0;
// Load default style from attribute, if specified...
- uint32_t def_style_bag_type_set_flags = 0;
+ uint32_t def_style_flags = 0u;
if (def_style_attr != 0) {
Res_value value;
- if (theme->getAttribute(def_style_attr, &value, &def_style_bag_type_set_flags) >= 0) {
+ if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
if (value.dataType == Res_value::TYPE_REFERENCE) {
def_style_res = value.data;
}
}
}
- // Now lock down the resource object and start pulling stuff from it.
- res.lock();
-
// Retrieve the default style bag, if requested.
- const ResTable::bag_entry* def_style_start = nullptr;
- uint32_t def_style_type_set_flags = 0;
- ssize_t bag_off = def_style_res != 0
- ? res.getBagLocked(def_style_res, &def_style_start,
- &def_style_type_set_flags)
- : -1;
- def_style_type_set_flags |= def_style_bag_type_set_flags;
- const ResTable::bag_entry* const def_style_end =
- def_style_start + (bag_off >= 0 ? bag_off : 0);
- BagAttributeFinder def_style_attr_finder(def_style_start, def_style_end);
+ const ResolvedBag* default_style_bag = nullptr;
+ if (def_style_res != 0) {
+ default_style_bag = assetmanager->GetBag(def_style_res);
+ if (default_style_bag != nullptr) {
+ def_style_flags |= default_style_bag->type_spec_flags;
+ }
+ }
+
+ BagAttributeFinder def_style_attr_finder(default_style_bag);
// Now iterate through all of the attributes that the client has requested,
// filling in each with whatever data we can find.
@@ -106,7 +106,7 @@
ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
}
- ssize_t block = -1;
+ ApkAssetsCookie cookie = kInvalidCookie;
uint32_t type_set_flags = 0;
value.dataType = Res_value::TYPE_NULL;
@@ -122,15 +122,14 @@
value.dataType = Res_value::TYPE_ATTRIBUTE;
value.data = src_values[ii];
if (kDebugStyles) {
- ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType,
- value.data);
+ ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data);
}
} else {
- const ResTable::bag_entry* const def_style_entry = def_style_attr_finder.Find(cur_ident);
- if (def_style_entry != def_style_end) {
- block = def_style_entry->stringBlock;
- type_set_flags = def_style_type_set_flags;
- value = def_style_entry->map.value;
+ const ResolvedBag::Entry* const entry = def_style_attr_finder.Find(cur_ident);
+ if (entry != def_style_attr_finder.end()) {
+ cookie = entry->cookie;
+ type_set_flags = def_style_flags;
+ value = entry->value;
if (kDebugStyles) {
ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
@@ -140,22 +139,26 @@
uint32_t resid = 0;
if (value.dataType != Res_value::TYPE_NULL) {
// Take care of resolving the found resource to its final value.
- ssize_t new_block =
- theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config);
- if (new_block >= 0) block = new_block;
+ ApkAssetsCookie new_cookie =
+ theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
+ if (new_cookie != kInvalidCookie) {
+ cookie = new_cookie;
+ }
if (kDebugStyles) {
ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
}
} else if (value.data != Res_value::DATA_NULL_EMPTY) {
- // If we still don't have a value for this attribute, try to find
- // it in the theme!
- ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags);
- if (new_block >= 0) {
+ // If we still don't have a value for this attribute, try to find it in the theme!
+ ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
+ if (new_cookie != kInvalidCookie) {
if (kDebugStyles) {
ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
- new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config);
- if (new_block >= 0) block = new_block;
+ new_cookie =
+ assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
+ if (new_cookie != kInvalidCookie) {
+ cookie = new_cookie;
+ }
if (kDebugStyles) {
ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
@@ -169,7 +172,7 @@
}
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
- block = -1;
+ cookie = kInvalidCookie;
}
if (kDebugStyles) {
@@ -179,9 +182,7 @@
// Write the final value back to Java.
out_values[STYLE_TYPE] = value.dataType;
out_values[STYLE_DATA] = value.data;
- out_values[STYLE_ASSET_COOKIE] =
- block != -1 ? static_cast<uint32_t>(res.getTableCookie(block))
- : static_cast<uint32_t>(-1);
+ out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
out_values[STYLE_RESOURCE_ID] = resid;
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
@@ -195,90 +196,80 @@
out_values += STYLE_NUM_ENTRIES;
}
- res.unlock();
-
if (out_indices != nullptr) {
out_indices[0] = indices_idx;
}
return true;
}
-void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
- uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length,
+void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
+ uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length,
uint32_t* out_values, uint32_t* out_indices) {
if (kDebugStyles) {
- ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p",
- theme, def_style_attr, def_style_res, xml_parser);
+ ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme,
+ def_style_attr, def_style_resid, xml_parser);
}
- const ResTable& res = theme->getResTable();
+ AssetManager2* assetmanager = theme->GetAssetManager();
ResTable_config config;
Res_value value;
int indices_idx = 0;
// Load default style from attribute, if specified...
- uint32_t def_style_bag_type_set_flags = 0;
+ uint32_t def_style_flags = 0u;
if (def_style_attr != 0) {
Res_value value;
- if (theme->getAttribute(def_style_attr, &value,
- &def_style_bag_type_set_flags) >= 0) {
+ if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
if (value.dataType == Res_value::TYPE_REFERENCE) {
- def_style_res = value.data;
+ def_style_resid = value.data;
}
}
}
- // Retrieve the style class associated with the current XML tag.
- int style = 0;
- uint32_t style_bag_type_set_flags = 0;
+ // Retrieve the style resource ID associated with the current XML tag's style attribute.
+ uint32_t style_resid = 0u;
+ uint32_t style_flags = 0u;
if (xml_parser != nullptr) {
ssize_t idx = xml_parser->indexOfStyle();
if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) {
if (value.dataType == value.TYPE_ATTRIBUTE) {
- if (theme->getAttribute(value.data, &value, &style_bag_type_set_flags) < 0) {
+ // Resolve the attribute with out theme.
+ if (theme->GetAttribute(value.data, &value, &style_flags) == kInvalidCookie) {
value.dataType = Res_value::TYPE_NULL;
}
}
+
if (value.dataType == value.TYPE_REFERENCE) {
- style = value.data;
+ style_resid = value.data;
}
}
}
- // Now lock down the resource object and start pulling stuff from it.
- res.lock();
-
// Retrieve the default style bag, if requested.
- const ResTable::bag_entry* def_style_attr_start = nullptr;
- uint32_t def_style_type_set_flags = 0;
- ssize_t bag_off = def_style_res != 0
- ? res.getBagLocked(def_style_res, &def_style_attr_start,
- &def_style_type_set_flags)
- : -1;
- def_style_type_set_flags |= def_style_bag_type_set_flags;
- const ResTable::bag_entry* const def_style_attr_end =
- def_style_attr_start + (bag_off >= 0 ? bag_off : 0);
- BagAttributeFinder def_style_attr_finder(def_style_attr_start,
- def_style_attr_end);
+ const ResolvedBag* default_style_bag = nullptr;
+ if (def_style_resid != 0) {
+ default_style_bag = assetmanager->GetBag(def_style_resid);
+ if (default_style_bag != nullptr) {
+ def_style_flags |= default_style_bag->type_spec_flags;
+ }
+ }
+
+ BagAttributeFinder def_style_attr_finder(default_style_bag);
// Retrieve the style class bag, if requested.
- const ResTable::bag_entry* style_attr_start = nullptr;
- uint32_t style_type_set_flags = 0;
- bag_off =
- style != 0
- ? res.getBagLocked(style, &style_attr_start, &style_type_set_flags)
- : -1;
- style_type_set_flags |= style_bag_type_set_flags;
- const ResTable::bag_entry* const style_attr_end =
- style_attr_start + (bag_off >= 0 ? bag_off : 0);
- BagAttributeFinder style_attr_finder(style_attr_start, style_attr_end);
+ const ResolvedBag* xml_style_bag = nullptr;
+ if (style_resid != 0) {
+ xml_style_bag = assetmanager->GetBag(style_resid);
+ if (xml_style_bag != nullptr) {
+ style_flags |= xml_style_bag->type_spec_flags;
+ }
+ }
+
+ BagAttributeFinder xml_style_attr_finder(xml_style_bag);
// Retrieve the XML attributes, if requested.
- static const ssize_t kXmlBlock = 0x10000000;
XmlAttributeFinder xml_attr_finder(xml_parser);
- const size_t xml_attr_end =
- xml_parser != nullptr ? xml_parser->getAttributeCount() : 0;
// Now iterate through all of the attributes that the client has requested,
// filling in each with whatever data we can find.
@@ -289,8 +280,8 @@
ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
}
- ssize_t block = kXmlBlock;
- uint32_t type_set_flags = 0;
+ ApkAssetsCookie cookie = kInvalidCookie;
+ uint32_t type_set_flags = 0u;
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
@@ -302,7 +293,7 @@
// Walk through the xml attributes looking for the requested attribute.
const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident);
- if (xml_attr_idx != xml_attr_end) {
+ if (xml_attr_idx != xml_attr_finder.end()) {
// We found the attribute we were looking for.
xml_parser->getAttributeValue(xml_attr_idx, &value);
if (kDebugStyles) {
@@ -312,12 +303,12 @@
if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
// Walk through the style class values looking for the requested attribute.
- const ResTable::bag_entry* const style_attr_entry = style_attr_finder.Find(cur_ident);
- if (style_attr_entry != style_attr_end) {
+ const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident);
+ if (entry != xml_style_attr_finder.end()) {
// We found the attribute we were looking for.
- block = style_attr_entry->stringBlock;
- type_set_flags = style_type_set_flags;
- value = style_attr_entry->map.value;
+ cookie = entry->cookie;
+ type_set_flags = style_flags;
+ value = entry->value;
if (kDebugStyles) {
ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
@@ -326,25 +317,25 @@
if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
// Walk through the default style values looking for the requested attribute.
- const ResTable::bag_entry* const def_style_attr_entry = def_style_attr_finder.Find(cur_ident);
- if (def_style_attr_entry != def_style_attr_end) {
+ const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident);
+ if (entry != def_style_attr_finder.end()) {
// We found the attribute we were looking for.
- block = def_style_attr_entry->stringBlock;
- type_set_flags = style_type_set_flags;
- value = def_style_attr_entry->map.value;
+ cookie = entry->cookie;
+ type_set_flags = def_style_flags;
+ value = entry->value;
if (kDebugStyles) {
ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
}
}
}
- uint32_t resid = 0;
+ uint32_t resid = 0u;
if (value.dataType != Res_value::TYPE_NULL) {
// Take care of resolving the found resource to its final value.
- ssize_t new_block =
- theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config);
- if (new_block >= 0) {
- block = new_block;
+ ApkAssetsCookie new_cookie =
+ theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
+ if (new_cookie != kInvalidCookie) {
+ cookie = new_cookie;
}
if (kDebugStyles) {
@@ -352,14 +343,15 @@
}
} else if (value.data != Res_value::DATA_NULL_EMPTY) {
// If we still don't have a value for this attribute, try to find it in the theme!
- ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags);
- if (new_block >= 0) {
+ ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
+ if (new_cookie != kInvalidCookie) {
if (kDebugStyles) {
ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
}
- new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config);
- if (new_block >= 0) {
- block = new_block;
+ new_cookie =
+ assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
+ if (new_cookie != kInvalidCookie) {
+ cookie = new_cookie;
}
if (kDebugStyles) {
@@ -375,7 +367,7 @@
}
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
- block = kXmlBlock;
+ cookie = kInvalidCookie;
}
if (kDebugStyles) {
@@ -385,9 +377,7 @@
// Write the final value back to Java.
out_values[STYLE_TYPE] = value.dataType;
out_values[STYLE_DATA] = value.data;
- out_values[STYLE_ASSET_COOKIE] =
- block != kXmlBlock ? static_cast<uint32_t>(res.getTableCookie(block))
- : static_cast<uint32_t>(-1);
+ out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
out_values[STYLE_RESOURCE_ID] = resid;
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
@@ -402,36 +392,28 @@
out_values += STYLE_NUM_ENTRIES;
}
- res.unlock();
-
// out_indices must NOT be nullptr.
out_indices[0] = indices_idx;
}
-bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser,
- uint32_t* attrs, size_t attrs_length,
- uint32_t* out_values, uint32_t* out_indices) {
+bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs,
+ size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
ResTable_config config;
Res_value value;
int indices_idx = 0;
- // Now lock down the resource object and start pulling stuff from it.
- res->lock();
-
// Retrieve the XML attributes, if requested.
const size_t xml_attr_count = xml_parser->getAttributeCount();
size_t ix = 0;
uint32_t cur_xml_attr = xml_parser->getAttributeNameResID(ix);
- static const ssize_t kXmlBlock = 0x10000000;
-
// Now iterate through all of the attributes that the client has requested,
// filling in each with whatever data we can find.
for (size_t ii = 0; ii < attrs_length; ii++) {
const uint32_t cur_ident = attrs[ii];
- ssize_t block = kXmlBlock;
- uint32_t type_set_flags = 0;
+ ApkAssetsCookie cookie = kInvalidCookie;
+ uint32_t type_set_flags = 0u;
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
@@ -450,28 +432,27 @@
cur_xml_attr = xml_parser->getAttributeNameResID(ix);
}
- uint32_t resid = 0;
+ uint32_t resid = 0u;
if (value.dataType != Res_value::TYPE_NULL) {
// Take care of resolving the found resource to its final value.
- // printf("Resolving attribute reference\n");
- ssize_t new_block = res->resolveReference(&value, block, &resid,
- &type_set_flags, &config);
- if (new_block >= 0) block = new_block;
+ ApkAssetsCookie new_cookie =
+ assetmanager->ResolveReference(cookie, &value, &config, &type_set_flags, &resid);
+ if (new_cookie != kInvalidCookie) {
+ cookie = new_cookie;
+ }
}
// Deal with the special @null value -- it turns back to TYPE_NULL.
if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
value.dataType = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
- block = kXmlBlock;
+ cookie = kInvalidCookie;
}
// Write the final value back to Java.
out_values[STYLE_TYPE] = value.dataType;
out_values[STYLE_DATA] = value.data;
- out_values[STYLE_ASSET_COOKIE] =
- block != kXmlBlock ? static_cast<uint32_t>(res->getTableCookie(block))
- : static_cast<uint32_t>(-1);
+ out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
out_values[STYLE_RESOURCE_ID] = resid;
out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
out_values[STYLE_DENSITY] = config.density;
@@ -485,8 +466,6 @@
out_values += STYLE_NUM_ENTRIES;
}
- res->unlock();
-
if (out_indices != nullptr) {
out_indices[0] = indices_idx;
}
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 28548e2..1d2c597 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -44,44 +44,6 @@
constexpr const static int kAppPackageId = 0x7f;
-// Element of a TypeSpec array. See TypeSpec.
-struct Type {
- // The configuration for which this type defines entries.
- // This is already converted to host endianness.
- ResTable_config configuration;
-
- // Pointer to the mmapped data where entry definitions are kept.
- const ResTable_type* type;
-};
-
-// TypeSpec is going to be immediately proceeded by
-// an array of Type structs, all in the same block of memory.
-struct TypeSpec {
- // Pointer to the mmapped data where flags are kept.
- // Flags denote whether the resource entry is public
- // and under which configurations it varies.
- const ResTable_typeSpec* type_spec;
-
- // Pointer to the mmapped data where the IDMAP mappings for this type
- // exist. May be nullptr if no IDMAP exists.
- const IdmapEntry_header* idmap_entries;
-
- // The number of types that follow this struct.
- // There is a type for each configuration
- // that entries are defined for.
- size_t type_count;
-
- // Trick to easily access a variable number of Type structs
- // proceeding this struct, and to ensure their alignment.
- const Type types[0];
-};
-
-// TypeSpecPtr points to the block of memory that holds
-// a TypeSpec struct, followed by an array of Type structs.
-// TypeSpecPtr is a managed pointer that knows how to delete
-// itself.
-using TypeSpecPtr = util::unique_cptr<TypeSpec>;
-
namespace {
// Builder that helps accumulate Type structs and then create a single
@@ -95,21 +57,22 @@
}
void AddType(const ResTable_type* type) {
- ResTable_config config;
- config.copyFromDtoH(type->config);
- types_.push_back(Type{config, type});
+ types_.push_back(type);
}
TypeSpecPtr Build() {
// Check for overflow.
- if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(Type) < types_.size()) {
+ using ElementType = const ResTable_type*;
+ if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) <
+ types_.size()) {
return {};
}
- TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type)));
+ TypeSpec* type_spec =
+ (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType)));
type_spec->type_spec = header_;
type_spec->idmap_entries = idmap_header_;
type_spec->type_count = types_.size();
- memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type));
+ memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType));
return TypeSpecPtr(type_spec);
}
@@ -118,7 +81,7 @@
const ResTable_typeSpec* header_;
const IdmapEntry_header* idmap_header_;
- std::vector<Type> types_;
+ std::vector<const ResTable_type*> types_;
};
} // namespace
@@ -162,18 +125,17 @@
return true;
}
-static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset,
- size_t entry_idx) {
+static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset) {
// Check that the offset is aligned.
if (entry_offset & 0x03) {
- LOG(ERROR) << "Entry offset at index " << entry_idx << " is not 4-byte aligned.";
+ LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned.";
return false;
}
// Check that the offset doesn't overflow.
if (entry_offset > std::numeric_limits<uint32_t>::max() - dtohl(type->entriesStart)) {
// Overflow in offset.
- LOG(ERROR) << "Entry offset at index " << entry_idx << " is too large.";
+ LOG(ERROR) << "Entry at offset " << entry_offset << " is too large.";
return false;
}
@@ -181,7 +143,7 @@
entry_offset += dtohl(type->entriesStart);
if (entry_offset > chunk_size - sizeof(ResTable_entry)) {
- LOG(ERROR) << "Entry offset at index " << entry_idx
+ LOG(ERROR) << "Entry at offset " << entry_offset
<< " is too large. No room for ResTable_entry.";
return false;
}
@@ -191,13 +153,13 @@
const size_t entry_size = dtohs(entry->size);
if (entry_size < sizeof(*entry)) {
- LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
+ LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
<< " is too small.";
return false;
}
if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) {
- LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
+ LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
<< " is too large.";
return false;
}
@@ -205,7 +167,7 @@
if (entry_size < sizeof(ResTable_map_entry)) {
// There needs to be room for one Res_value struct.
if (entry_offset + entry_size > chunk_size - sizeof(Res_value)) {
- LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << entry_idx
+ LOG(ERROR) << "No room for Res_value after ResTable_entry at offset " << entry_offset
<< " for type " << (int)type->id << ".";
return false;
}
@@ -214,12 +176,12 @@
reinterpret_cast<const Res_value*>(reinterpret_cast<const uint8_t*>(entry) + entry_size);
const size_t value_size = dtohs(value->size);
if (value_size < sizeof(Res_value)) {
- LOG(ERROR) << "Res_value at index " << entry_idx << " is too small.";
+ LOG(ERROR) << "Res_value at offset " << entry_offset << " is too small.";
return false;
}
if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) {
- LOG(ERROR) << "Res_value size " << value_size << " at index " << entry_idx
+ LOG(ERROR) << "Res_value size " << value_size << " at offset " << entry_offset
<< " is too large.";
return false;
}
@@ -228,119 +190,76 @@
const size_t map_entry_count = dtohl(map->count);
size_t map_entries_start = entry_offset + entry_size;
if (map_entries_start & 0x03) {
- LOG(ERROR) << "Map entries at index " << entry_idx << " start at unaligned offset.";
+ LOG(ERROR) << "Map entries at offset " << entry_offset << " start at unaligned offset.";
return false;
}
// Each entry is sizeof(ResTable_map) big.
if (map_entry_count > ((chunk_size - map_entries_start) / sizeof(ResTable_map))) {
- LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << entry_idx << ".";
+ LOG(ERROR) << "Too many map entries in ResTable_map_entry at offset " << entry_offset << ".";
return false;
}
}
return true;
}
-bool LoadedPackage::FindEntry(const TypeSpecPtr& type_spec_ptr, uint16_t entry_idx,
- const ResTable_config& config, FindEntryResult* out_entry) const {
- const ResTable_config* best_config = nullptr;
- const ResTable_type* best_type = nullptr;
- uint32_t best_offset = 0;
+const ResTable_entry* LoadedPackage::GetEntry(const ResTable_type* type_chunk,
+ uint16_t entry_index) {
+ uint32_t entry_offset = GetEntryOffset(type_chunk, entry_index);
+ if (entry_offset == ResTable_type::NO_ENTRY) {
+ return nullptr;
+ }
+ return GetEntryFromOffset(type_chunk, entry_offset);
+}
- for (uint32_t i = 0; i < type_spec_ptr->type_count; i++) {
- const Type* type = &type_spec_ptr->types[i];
- const ResTable_type* type_chunk = type->type;
+uint32_t LoadedPackage::GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index) {
+ // The configuration matches and is better than the previous selection.
+ // Find the entry value if it exists for this configuration.
+ const size_t entry_count = dtohl(type_chunk->entryCount);
+ const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
- if (type->configuration.match(config) &&
- (best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) {
- // The configuration matches and is better than the previous selection.
- // Find the entry value if it exists for this configuration.
- const size_t entry_count = dtohl(type_chunk->entryCount);
- const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
+ // Check if there is the desired entry in this type.
- // Check if there is the desired entry in this type.
-
- if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
- // This is encoded as a sparse map, so perform a binary search.
- const ResTable_sparseTypeEntry* sparse_indices =
- reinterpret_cast<const ResTable_sparseTypeEntry*>(
- reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
- const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count;
- const ResTable_sparseTypeEntry* result =
- std::lower_bound(sparse_indices, sparse_indices_end, entry_idx,
- [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) {
- return dtohs(entry.idx) < entry_idx;
- });
-
- if (result == sparse_indices_end || dtohs(result->idx) != entry_idx) {
- // No entry found.
- continue;
- }
-
- // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as
- // the real offset divided by 4.
- best_offset = uint32_t{dtohs(result->offset)} * 4u;
- } else {
- if (entry_idx >= entry_count) {
- // This entry cannot be here.
- continue;
- }
-
- const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
+ if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
+ // This is encoded as a sparse map, so perform a binary search.
+ const ResTable_sparseTypeEntry* sparse_indices =
+ reinterpret_cast<const ResTable_sparseTypeEntry*>(
reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
- const uint32_t offset = dtohl(entry_offsets[entry_idx]);
- if (offset == ResTable_type::NO_ENTRY) {
- continue;
- }
+ const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count;
+ const ResTable_sparseTypeEntry* result =
+ std::lower_bound(sparse_indices, sparse_indices_end, entry_index,
+ [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) {
+ return dtohs(entry.idx) < entry_idx;
+ });
- // There is an entry for this resource, record it.
- best_offset = offset;
- }
-
- best_config = &type->configuration;
- best_type = type_chunk;
+ if (result == sparse_indices_end || dtohs(result->idx) != entry_index) {
+ // No entry found.
+ return ResTable_type::NO_ENTRY;
}
+
+ // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as
+ // the real offset divided by 4.
+ return uint32_t{dtohs(result->offset)} * 4u;
}
- if (best_type == nullptr) {
- return false;
+ // This type is encoded as a dense array.
+ if (entry_index >= entry_count) {
+ // This entry cannot be here.
+ return ResTable_type::NO_ENTRY;
}
- if (UNLIKELY(!VerifyResTableEntry(best_type, best_offset, entry_idx))) {
- return false;
- }
-
- const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
- reinterpret_cast<const uint8_t*>(best_type) + best_offset + dtohl(best_type->entriesStart));
-
- const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec_ptr->type_spec + 1);
- out_entry->type_flags = dtohl(flags[entry_idx]);
- out_entry->entry = best_entry;
- out_entry->config = best_config;
- out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1);
- out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index));
- return true;
+ const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
+ return dtohl(entry_offsets[entry_index]);
}
-bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
- FindEntryResult* out_entry) const {
- ATRACE_CALL();
-
- // If the type IDs are offset in this package, we need to take that into account when searching
- // for a type.
- const TypeSpecPtr& ptr = type_specs_[type_idx - type_id_offset_];
- if (UNLIKELY(ptr == nullptr)) {
- return false;
+const ResTable_entry* LoadedPackage::GetEntryFromOffset(const ResTable_type* type_chunk,
+ uint32_t offset) {
+ if (UNLIKELY(!VerifyResTableEntry(type_chunk, offset))) {
+ return nullptr;
}
-
- // If there is an IDMAP supplied with this package, translate the entry ID.
- if (ptr->idmap_entries != nullptr) {
- if (!LoadedIdmap::Lookup(ptr->idmap_entries, entry_idx, &entry_idx)) {
- // There is no mapping, so the resource is not meant to be in this overlay package.
- return false;
- }
- }
- return FindEntry(ptr, entry_idx, config, out_entry);
+ return reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type_chunk) +
+ offset + dtohl(type_chunk->entriesStart));
}
void LoadedPackage::CollectConfigurations(bool exclude_mipmap,
@@ -348,7 +267,7 @@
const static std::u16string kMipMap = u"mipmap";
const size_t type_count = type_specs_.size();
for (size_t i = 0; i < type_count; i++) {
- const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i];
+ const TypeSpecPtr& type_spec = type_specs_[i];
if (type_spec != nullptr) {
if (exclude_mipmap) {
const int type_idx = type_spec->type_spec->id - 1;
@@ -369,8 +288,11 @@
}
}
- for (size_t j = 0; j < type_spec->type_count; j++) {
- out_configs->insert(type_spec->types[j].configuration);
+ const auto iter_end = type_spec->types + type_spec->type_count;
+ for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+ ResTable_config config;
+ config.copyFromDtoH((*iter)->config);
+ out_configs->insert(config);
}
}
}
@@ -380,10 +302,12 @@
char temp_locale[RESTABLE_MAX_LOCALE_LEN];
const size_t type_count = type_specs_.size();
for (size_t i = 0; i < type_count; i++) {
- const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i];
+ const TypeSpecPtr& type_spec = type_specs_[i];
if (type_spec != nullptr) {
- for (size_t j = 0; j < type_spec->type_count; j++) {
- const ResTable_config& configuration = type_spec->types[j].configuration;
+ const auto iter_end = type_spec->types + type_spec->type_count;
+ for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+ ResTable_config configuration;
+ configuration.copyFromDtoH((*iter)->config);
if (configuration.locale != 0) {
configuration.getBcp47Locale(temp_locale, canonicalize);
std::string locale(temp_locale);
@@ -411,17 +335,17 @@
return 0u;
}
- for (size_t ti = 0; ti < type_spec->type_count; ti++) {
- const Type* type = &type_spec->types[ti];
- size_t entry_count = dtohl(type->type->entryCount);
+ const auto iter_end = type_spec->types + type_spec->type_count;
+ for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+ const ResTable_type* type = *iter;
+ size_t entry_count = dtohl(type->entryCount);
for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
- reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize));
+ reinterpret_cast<const uint8_t*>(type) + dtohs(type->header.headerSize));
const uint32_t offset = dtohl(entry_offsets[entry_idx]);
if (offset != ResTable_type::NO_ENTRY) {
- const ResTable_entry* entry =
- reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type->type) +
- dtohl(type->type->entriesStart) + offset);
+ const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
+ reinterpret_cast<const uint8_t*>(type) + dtohl(type->entriesStart) + offset);
if (dtohl(entry->key.index) == static_cast<uint32_t>(key_idx)) {
// The package ID will be overridden by the caller (due to runtime assignment of package
// IDs for shared libraries).
@@ -433,8 +357,7 @@
return 0u;
}
-const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
- const uint8_t package_id = get_package_id(resid);
+const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const {
for (const auto& loaded_package : packages_) {
if (loaded_package->GetPackageId() == package_id) {
return loaded_package.get();
@@ -682,26 +605,6 @@
return std::move(loaded_package);
}
-bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config,
- FindEntryResult* out_entry) const {
- ATRACE_CALL();
-
- const uint8_t package_id = get_package_id(resid);
- const uint8_t type_id = get_type_id(resid);
- const uint16_t entry_id = get_entry_id(resid);
-
- if (UNLIKELY(type_id == 0)) {
- LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
- return false;
- }
-
- for (const auto& loaded_package : packages_) {
- if (loaded_package->GetPackageId() == package_id) {
- return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry);
- }
- }
- return false;
-}
bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
bool load_as_shared_library) {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index b033137..ef08897 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -69,6 +69,8 @@
Entry entries[0];
};
+struct FindEntryResult;
+
// AssetManager2 is the main entry point for accessing assets and resources.
// AssetManager2 provides caching of resources retrieved via the underlying ApkAssets.
class AssetManager2 {
@@ -127,7 +129,7 @@
// If `exclude_mipmap` is set to true, resource configurations defined for resource type 'mipmap'
// will be excluded from the list.
std::set<ResTable_config> GetResourceConfigurations(bool exclude_system = false,
- bool exclude_mipmap = false);
+ bool exclude_mipmap = false) const;
// Returns all the locales for which there are resources defined. This includes resource
// locales in all the ApkAssets set for this AssetManager.
@@ -136,24 +138,24 @@
// If `merge_equivalent_languages` is set to true, resource locales will be canonicalized
// and de-duped in the resulting list.
std::set<std::string> GetResourceLocales(bool exclude_system = false,
- bool merge_equivalent_languages = false);
+ bool merge_equivalent_languages = false) const;
// Searches the set of APKs loaded by this AssetManager and opens the first one found located
// in the assets/ directory.
// `mode` controls how the file is opened.
//
// NOTE: The loaded APKs are searched in reverse order.
- std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode);
+ std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode) const;
// Opens a file within the assets/ directory of the APK specified by `cookie`.
// `mode` controls how the file is opened.
std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie,
- Asset::AccessMode mode);
+ Asset::AccessMode mode) const;
// Opens the directory specified by `dirname`. The result is an AssetDir that is the combination
// of all directories matching `dirname` under the assets/ directory of every ApkAssets loaded.
// The entries are sorted by their ASCII name.
- std::unique_ptr<AssetDir> OpenDir(const std::string& dirname);
+ std::unique_ptr<AssetDir> OpenDir(const std::string& dirname) const;
// Searches the set of APKs loaded by this AssetManager and opens the first one found.
// `mode` controls how the file is opened.
@@ -161,24 +163,24 @@
//
// NOTE: The loaded APKs are searched in reverse order.
std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, Asset::AccessMode mode,
- ApkAssetsCookie* out_cookie = nullptr);
+ ApkAssetsCookie* out_cookie = nullptr) const;
// Opens a file in the APK specified by `cookie`. `mode` controls how the file is opened.
// This is typically used to open a specific AndroidManifest.xml, or a binary XML file
// referenced by a resource lookup with GetResource().
std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie,
- Asset::AccessMode mode);
+ Asset::AccessMode mode) const;
// Populates the `out_name` parameter with resource name information.
// Utf8 strings are preferred, and only if they are unavailable are
// the Utf16 variants populated.
// Returns false if the resource was not found or the name was missing/corrupt.
- bool GetResourceName(uint32_t resid, ResourceName* out_name);
+ bool GetResourceName(uint32_t resid, ResourceName* out_name) const;
// Populates `out_flags` with the bitmask of configuration axis that this resource varies with.
// See ResTable_config for the list of configuration axis.
// Returns false if the resource was not found.
- bool GetResourceFlags(uint32_t resid, uint32_t* out_flags);
+ bool GetResourceFlags(uint32_t resid, uint32_t* out_flags) const;
// Finds the resource ID assigned to `resource_name`.
// `resource_name` must be of the form '[package:][type/]entry'.
@@ -186,7 +188,7 @@
// If no type is specified in `resource_name`, then `fallback_type` is used as the type.
// Returns 0x0 if no resource by that name was found.
uint32_t GetResourceId(const std::string& resource_name, const std::string& fallback_type = {},
- const std::string& fallback_package = {});
+ const std::string& fallback_package = {}) const;
// Retrieves the best matching resource with ID `resid`. The resource value is filled into
// `out_value` and the configuration for the selected value is populated in `out_selected_config`.
@@ -199,7 +201,7 @@
// this function logs if the resource was a map/bag type before returning kInvalidCookie.
ApkAssetsCookie GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override,
Res_value* out_value, ResTable_config* out_selected_config,
- uint32_t* out_flags);
+ uint32_t* out_flags) const;
// Resolves the resource reference in `in_out_value` if the data type is
// Res_value::TYPE_REFERENCE.
@@ -215,7 +217,7 @@
// it was not found.
ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
ResTable_config* in_out_selected_config, uint32_t* in_out_flags,
- uint32_t* out_last_reference);
+ uint32_t* out_last_reference) const;
// Retrieves the best matching bag/map resource with ID `resid`.
// This method will resolve all parent references for this bag and merge keys with the child.
@@ -233,9 +235,9 @@
std::unique_ptr<Theme> NewTheme();
template <typename Func>
- void ForEachPackage(Func func) {
+ void ForEachPackage(Func func) const {
for (const PackageGroup& package_group : package_groups_) {
- func(package_group.packages_.front()->GetPackageName(),
+ func(package_group.packages_.front().loaded_package_->GetPackageName(),
package_group.dynamic_ref_table.mAssignedPackageId);
}
}
@@ -260,7 +262,7 @@
// NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly
// bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds.
ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match,
- FindEntryResult* out_entry);
+ FindEntryResult* out_entry) const;
// Assigns package IDs to all shared library ApkAssets.
// Should be called whenever the ApkAssets are changed.
@@ -270,13 +272,43 @@
// bitmask `diff`.
void InvalidateCaches(uint32_t diff);
+ // Triggers the re-construction of lists of types that match the set configuration.
+ // This should always be called when mutating the AssetManager's configuration or ApkAssets set.
+ void RebuildFilterList();
+
// The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
// have a longer lifetime.
std::vector<const ApkAssets*> apk_assets_;
+ // A collection of configurations and their associated ResTable_type that match the current
+ // AssetManager configuration.
+ struct FilteredConfigGroup {
+ std::vector<ResTable_config> configurations;
+ std::vector<const ResTable_type*> types;
+ };
+
+ // Represents an single package.
+ struct ConfiguredPackage {
+ // A pointer to the immutable, loaded package info.
+ const LoadedPackage* loaded_package_;
+
+ // A mutable AssetManager-specific list of configurations that match the AssetManager's
+ // current configuration. This is used as an optimization to avoid checking every single
+ // candidate configuration when looking up resources.
+ ByteBucketArray<FilteredConfigGroup> filtered_configs_;
+ };
+
+ // Represents a logical package, which can be made up of many individual packages. Each package
+ // in a PackageGroup shares the same package name and package ID.
struct PackageGroup {
- std::vector<const LoadedPackage*> packages_;
+ // The set of packages that make-up this group.
+ std::vector<ConfiguredPackage> packages_;
+
+ // The cookies associated with each package in the group. They share the same order as
+ // packages_.
std::vector<ApkAssetsCookie> cookies_;
+
+ // A library reference table that contains build-package ID to runtime-package ID mappings.
DynamicRefTable dynamic_ref_table;
};
@@ -350,7 +382,7 @@
ApkAssetsCookie ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
ResTable_config* in_out_selected_config = nullptr,
uint32_t* in_out_type_spec_flags = nullptr,
- uint32_t* out_last_ref = nullptr);
+ uint32_t* out_last_ref = nullptr) const;
private:
DISALLOW_COPY_AND_ASSIGN(Theme);
diff --git a/libs/androidfw/include/androidfw/AttributeFinder.h b/libs/androidfw/include/androidfw/AttributeFinder.h
index f281921..03fad49 100644
--- a/libs/androidfw/include/androidfw/AttributeFinder.h
+++ b/libs/androidfw/include/androidfw/AttributeFinder.h
@@ -58,6 +58,7 @@
BackTrackingAttributeFinder(const Iterator& begin, const Iterator& end);
Iterator Find(uint32_t attr);
+ inline Iterator end();
private:
void JumpToClosestAttribute(uint32_t package_id);
@@ -201,6 +202,11 @@
return end_;
}
+template <typename Derived, typename Iterator>
+Iterator BackTrackingAttributeFinder<Derived, Iterator>::end() {
+ return end_;
+}
+
} // namespace android
#endif // ANDROIDFW_ATTRIBUTE_FINDER_H
diff --git a/libs/androidfw/include/androidfw/AttributeResolution.h b/libs/androidfw/include/androidfw/AttributeResolution.h
index 69b76041..35ef98d 100644
--- a/libs/androidfw/include/androidfw/AttributeResolution.h
+++ b/libs/androidfw/include/androidfw/AttributeResolution.h
@@ -17,7 +17,8 @@
#ifndef ANDROIDFW_ATTRIBUTERESOLUTION_H
#define ANDROIDFW_ATTRIBUTERESOLUTION_H
-#include <androidfw/ResourceTypes.h>
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ResourceTypes.h"
namespace android {
@@ -42,19 +43,19 @@
// `out_values` must NOT be nullptr.
// `out_indices` may be nullptr.
-bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t def_style_res,
+bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_resid,
uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
size_t attrs_length, uint32_t* out_values, uint32_t* out_indices);
// `out_values` must NOT be nullptr.
// `out_indices` is NOT optional and must NOT be nullptr.
-void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
- uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length,
+void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
+ uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length,
uint32_t* out_values, uint32_t* out_indices);
// `out_values` must NOT be nullptr.
// `out_indices` may be nullptr.
-bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, uint32_t* attrs,
+bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs,
size_t attrs_length, uint32_t* out_values, uint32_t* out_indices);
} // namespace android
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 965e2db..35ae5fc 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -41,32 +41,40 @@
int package_id = 0;
};
-struct FindEntryResult {
- // A pointer to the resource table entry for this resource.
- // If the size of the entry is > sizeof(ResTable_entry), it can be cast to
- // a ResTable_map_entry and processed as a bag/map.
- const ResTable_entry* entry = nullptr;
+// TypeSpec is going to be immediately proceeded by
+// an array of Type structs, all in the same block of memory.
+struct TypeSpec {
+ // Pointer to the mmapped data where flags are kept.
+ // Flags denote whether the resource entry is public
+ // and under which configurations it varies.
+ const ResTable_typeSpec* type_spec;
- // The configuration for which the resulting entry was defined.
- const ResTable_config* config = nullptr;
+ // Pointer to the mmapped data where the IDMAP mappings for this type
+ // exist. May be nullptr if no IDMAP exists.
+ const IdmapEntry_header* idmap_entries;
- // Stores the resulting bitmask of configuration axis with which the resource value varies.
- uint32_t type_flags = 0u;
+ // The number of types that follow this struct.
+ // There is a type for each configuration that entries are defined for.
+ size_t type_count;
- // The dynamic package ID map for the package from which this resource came from.
- const DynamicRefTable* dynamic_ref_table = nullptr;
+ // Trick to easily access a variable number of Type structs
+ // proceeding this struct, and to ensure their alignment.
+ const ResTable_type* types[0];
- // The string pool reference to the type's name. This uses a different string pool than
- // the global string pool, but this is hidden from the caller.
- StringPoolRef type_string_ref;
+ inline uint32_t GetFlagsForEntryIndex(uint16_t entry_index) const {
+ if (entry_index >= dtohl(type_spec->entryCount)) {
+ return 0u;
+ }
- // The string pool reference to the entry's name. This uses a different string pool than
- // the global string pool, but this is hidden from the caller.
- StringPoolRef entry_string_ref;
+ const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec + 1);
+ return flags[entry_index];
+ }
};
-struct TypeSpec;
-class LoadedArsc;
+// TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of
+// ResTable_type pointers.
+// TypeSpecPtr is a managed pointer that knows how to delete itself.
+using TypeSpecPtr = util::unique_cptr<TypeSpec>;
class LoadedPackage {
public:
@@ -76,9 +84,6 @@
~LoadedPackage();
- bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
- FindEntryResult* out_entry) const;
-
// Finds the entry with the specified type name and entry name. The names are in UTF-16 because
// the underlying ResStringPool API expects this. For now this is acceptable, but since
// the default policy in AAPT2 is to build UTF-8 string pools, this needs to change.
@@ -86,6 +91,12 @@
// for patching the correct package ID to the resource ID.
uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const;
+ static const ResTable_entry* GetEntry(const ResTable_type* type_chunk, uint16_t entry_index);
+
+ static uint32_t GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index);
+
+ static const ResTable_entry* GetEntryFromOffset(const ResTable_type* type_chunk, uint32_t offset);
+
// Returns the string pool where type names are stored.
inline const ResStringPool* GetTypeStringPool() const {
return &type_string_pool_;
@@ -135,14 +146,32 @@
// before being inserted into the set. This may cause some equivalent locales to de-dupe.
void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const;
+ // type_idx is TT - 1 from 0xPPTTEEEE.
+ inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const {
+ // If the type IDs are offset in this package, we need to take that into account when searching
+ // for a type.
+ return type_specs_[type_index - type_id_offset_].get();
+ }
+
+ template <typename Func>
+ void ForEachTypeSpec(Func f) const {
+ for (size_t i = 0; i < type_specs_.size(); i++) {
+ const TypeSpecPtr& ptr = type_specs_[i];
+ if (ptr != nullptr) {
+ uint8_t type_id = ptr->type_spec->id;
+ if (ptr->idmap_entries != nullptr) {
+ type_id = ptr->idmap_entries->target_type_id;
+ }
+ f(ptr.get(), type_id - 1);
+ }
+ }
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
LoadedPackage();
- bool FindEntry(const util::unique_cptr<TypeSpec>& type_spec_ptr, uint16_t entry_idx,
- const ResTable_config& config, FindEntryResult* out_entry) const;
-
ResStringPool type_string_pool_;
ResStringPool key_string_pool_;
std::string package_name_;
@@ -152,7 +181,7 @@
bool system_ = false;
bool overlay_ = false;
- ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_;
+ ByteBucketArray<TypeSpecPtr> type_specs_;
std::vector<DynamicPackageEntry> dynamic_package_map_;
};
@@ -180,25 +209,20 @@
return &global_string_pool_;
}
- // Finds the resource with ID `resid` with the best value for configuration `config`.
- // The parameter `out_entry` will be filled with the resulting resource entry.
- // The resource entry can be a simple entry (ResTable_entry) or a complex bag
- // (ResTable_entry_map).
- bool FindEntry(uint32_t resid, const ResTable_config& config, FindEntryResult* out_entry) const;
-
- // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist.
- const LoadedPackage* GetPackageForId(uint32_t resid) const;
-
- // Returns true if this is a system provided resource.
- inline bool IsSystem() const {
- return system_;
- }
+ // Gets a pointer to the package with the specified package ID, or nullptr if no such package
+ // exists.
+ const LoadedPackage* GetPackageById(uint8_t package_id) const;
// Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc.
inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const {
return packages_;
}
+ // Returns true if this is a system provided resource.
+ inline bool IsSystem() const {
+ return system_;
+ }
+
private:
DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h
new file mode 100644
index 0000000..64924f4
--- /dev/null
+++ b/libs/androidfw/include/androidfw/MutexGuard.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef ANDROIDFW_MUTEXGUARD_H
+#define ANDROIDFW_MUTEXGUARD_H
+
+#include <mutex>
+#include <type_traits>
+
+#include "android-base/macros.h"
+
+namespace android {
+
+template <typename T>
+class ScopedLock;
+
+// Owns the guarded object and protects access to it via a mutex.
+// The guarded object is inaccessible via this class.
+// The mutex is locked and the object accessed via the ScopedLock<T> class.
+//
+// NOTE: The template parameter T should not be a raw pointer, since ownership
+// is ambiguous and error-prone. Instead use an std::unique_ptr<>.
+//
+// Example use:
+//
+// Guarded<std::string> shared_string("hello");
+// {
+// ScopedLock<std::string> locked_string(shared_string);
+// *locked_string += " world";
+// }
+//
+template <typename T>
+class Guarded {
+ static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer");
+
+ public:
+ explicit Guarded() : guarded_() {
+ }
+
+ template <typename U = T>
+ explicit Guarded(const T& guarded,
+ typename std::enable_if<std::is_copy_constructible<U>::value>::type = void())
+ : guarded_(guarded) {
+ }
+
+ template <typename U = T>
+ explicit Guarded(T&& guarded,
+ typename std::enable_if<std::is_move_constructible<U>::value>::type = void())
+ : guarded_(std::move(guarded)) {
+ }
+
+ private:
+ friend class ScopedLock<T>;
+
+ DISALLOW_COPY_AND_ASSIGN(Guarded);
+
+ std::mutex lock_;
+ T guarded_;
+};
+
+template <typename T>
+class ScopedLock {
+ public:
+ explicit ScopedLock(Guarded<T>& guarded) : lock_(guarded.lock_), guarded_(guarded.guarded_) {
+ }
+
+ T& operator*() {
+ return guarded_;
+ }
+
+ T* operator->() {
+ return &guarded_;
+ }
+
+ T* get() {
+ return &guarded_;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedLock);
+
+ std::lock_guard<std::mutex> lock_;
+ T& guarded_;
+};
+
+} // namespace android
+
+#endif // ANDROIDFW_MUTEXGUARD_H
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 6c43a67..e2b9f00 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -26,58 +26,56 @@
using ::android::base::unique_fd;
using ::com::android::basic::R;
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::NotNull;
+using ::testing::SizeIs;
+using ::testing::StrEq;
namespace android {
TEST(ApkAssetsTest, LoadApk) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
- ASSERT_NE(nullptr, loaded_arsc);
-
- const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
- ASSERT_NE(nullptr, loaded_package);
-
- std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
- ASSERT_NE(nullptr, asset);
+ ASSERT_THAT(loaded_arsc, NotNull());
+ ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
+ ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
}
TEST(ApkAssetsTest, LoadApkFromFd) {
const std::string path = GetTestDataPath() + "/basic/basic.apk";
unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY));
- ASSERT_GE(fd.get(), 0);
+ ASSERT_THAT(fd.get(), Ge(0));
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::LoadFromFd(std::move(fd), path, false /*system*/, false /*force_shared_lib*/);
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
- ASSERT_NE(nullptr, loaded_arsc);
-
- const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
- ASSERT_NE(nullptr, loaded_package);
-
- std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
- ASSERT_NE(nullptr, asset);
+ ASSERT_THAT(loaded_arsc, NotNull());
+ ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
+ ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
}
TEST(ApkAssetsTest, LoadApkAsSharedLibrary) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk");
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
+
const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
- ASSERT_NE(nullptr, loaded_arsc);
- ASSERT_EQ(1u, loaded_arsc->GetPackages().size());
+ ASSERT_THAT(loaded_arsc, NotNull());
+ ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u));
EXPECT_FALSE(loaded_arsc->GetPackages()[0]->IsDynamic());
loaded_apk = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk");
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
loaded_arsc = loaded_apk->GetLoadedArsc();
- ASSERT_NE(nullptr, loaded_arsc);
- ASSERT_EQ(1u, loaded_arsc->GetPackages().size());
+ ASSERT_THAT(loaded_arsc, NotNull());
+ ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u));
EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic());
}
@@ -86,19 +84,22 @@
ResTable target_table;
const std::string target_path = GetTestDataPath() + "/basic/basic.apk";
ASSERT_TRUE(ReadFileFromZipToString(target_path, "resources.arsc", &contents));
- ASSERT_EQ(NO_ERROR, target_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
+ ASSERT_THAT(target_table.add(contents.data(), contents.size(), 0, true /*copyData*/),
+ Eq(NO_ERROR));
ResTable overlay_table;
const std::string overlay_path = GetTestDataPath() + "/overlay/overlay.apk";
ASSERT_TRUE(ReadFileFromZipToString(overlay_path, "resources.arsc", &contents));
- ASSERT_EQ(NO_ERROR, overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
+ ASSERT_THAT(overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/),
+ Eq(NO_ERROR));
util::unique_cptr<void> idmap_data;
void* temp_data;
size_t idmap_len;
- ASSERT_EQ(NO_ERROR, target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(),
- overlay_path.c_str(), &temp_data, &idmap_len));
+ ASSERT_THAT(target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(),
+ overlay_path.c_str(), &temp_data, &idmap_len),
+ Eq(NO_ERROR));
idmap_data.reset(temp_data);
TemporaryFile tf;
@@ -108,37 +109,30 @@
// Open something so that the destructor of TemporaryFile closes a valid fd.
tf.fd = open("/dev/null", O_WRONLY);
- std::unique_ptr<const ApkAssets> loaded_overlay_apk = ApkAssets::LoadOverlay(tf.path);
- ASSERT_NE(nullptr, loaded_overlay_apk);
+ ASSERT_THAT(ApkAssets::LoadOverlay(tf.path), NotNull());
}
TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
- {
- std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
- ASSERT_NE(nullptr, assets);
- }
+ { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
- {
- std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
- ASSERT_NE(nullptr, assets);
- }
+ { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
}
TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
- ASSERT_NE(nullptr, loaded_apk);
+ ASSERT_THAT(loaded_apk, NotNull());
auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN);
- ASSERT_NE(nullptr, asset);
+ ASSERT_THAT(asset, NotNull());
off64_t start, length;
unique_fd fd(asset->openFileDescriptor(&start, &length));
- EXPECT_GE(fd.get(), 0);
+ ASSERT_THAT(fd.get(), Ge(0));
lseek64(fd.get(), start, SEEK_SET);
@@ -146,7 +140,7 @@
buffer.resize(length);
ASSERT_TRUE(base::ReadFully(fd.get(), &*buffer.begin(), length));
- EXPECT_EQ("This should be uncompressed.\n\n", buffer);
+ EXPECT_THAT(buffer, StrEq("This should be uncompressed.\n\n"));
}
} // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 85e8f25..437e147 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -81,17 +81,18 @@
}
BENCHMARK(BM_AssetManagerLoadFrameworkAssetsOld);
-static void BM_AssetManagerGetResource(benchmark::State& state) {
- GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
- basic::R::integer::number1, state);
+static void BM_AssetManagerGetResource(benchmark::State& state, uint32_t resid) {
+ GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid, state);
}
-BENCHMARK(BM_AssetManagerGetResource);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResource, number1, basic::R::integer::number1);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResource, deep_ref, basic::R::integer::deep_ref);
-static void BM_AssetManagerGetResourceOld(benchmark::State& state) {
- GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
- basic::R::integer::number1, state);
+static void BM_AssetManagerGetResourceOld(benchmark::State& state, uint32_t resid) {
+ GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid,
+ state);
}
-BENCHMARK(BM_AssetManagerGetResourceOld);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, number1, basic::R::integer::number1);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, deep_ref, basic::R::integer::deep_ref);
static void BM_AssetManagerGetLibraryResource(benchmark::State& state) {
GetResourceBenchmark(
@@ -196,7 +197,7 @@
static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) {
AssetManager assets;
if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/,
- false /*isSystemAssets*/)) {
+ true /*isSystemAssets*/)) {
state.SkipWithError("Failed to load assets");
return;
}
@@ -211,4 +212,44 @@
}
BENCHMARK(BM_AssetManagerGetResourceLocalesOld);
+static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) {
+ std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
+ if (apk == nullptr) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ AssetManager2 assets;
+ assets.SetApkAssets({apk.get()});
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+
+ while (state.KeepRunning()) {
+ config.sdkVersion = ~config.sdkVersion;
+ assets.SetConfiguration(config);
+ }
+}
+BENCHMARK(BM_AssetManagerSetConfigurationFramework);
+
+static void BM_AssetManagerSetConfigurationFrameworkOld(benchmark::State& state) {
+ AssetManager assets;
+ if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/,
+ true /*isSystemAssets*/)) {
+ state.SkipWithError("Failed to load assets");
+ return;
+ }
+
+ const ResTable& table = assets.getResources(true);
+
+ ResTable_config config;
+ memset(&config, 0, sizeof(config));
+
+ while (state.KeepRunning()) {
+ config.sdkVersion = ~config.sdkVersion;
+ assets.setConfiguration(config);
+ }
+}
+BENCHMARK(BM_AssetManagerSetConfigurationFrameworkOld);
+
} // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp
new file mode 100644
index 0000000..fa300c5
--- /dev/null
+++ b/libs/androidfw/tests/AttributeResolution_bench.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "benchmark/benchmark.h"
+
+//#include "android-base/stringprintf.h"
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/AttributeResolution.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "BenchmarkHelpers.h"
+#include "data/basic/R.h"
+#include "data/styles/R.h"
+
+namespace app = com::android::app;
+namespace basic = com::android::basic;
+
+namespace android {
+
+constexpr const static char* kFrameworkPath = "/system/framework/framework-res.apk";
+constexpr const static uint32_t Theme_Material_Light = 0x01030237u;
+
+static void BM_ApplyStyle(benchmark::State& state) {
+ std::unique_ptr<const ApkAssets> styles_apk =
+ ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+ if (styles_apk == nullptr) {
+ state.SkipWithError("failed to load assets");
+ return;
+ }
+
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({styles_apk.get()});
+
+ std::unique_ptr<Asset> asset =
+ assetmanager.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER);
+ if (asset == nullptr) {
+ state.SkipWithError("failed to load layout");
+ return;
+ }
+
+ ResXMLTree xml_tree;
+ if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) {
+ state.SkipWithError("corrupt xml layout");
+ return;
+ }
+
+ // Skip to the first tag.
+ while (xml_tree.next() != ResXMLParser::START_TAG) {
+ }
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ theme->ApplyStyle(app::R::style::StyleTwo);
+
+ std::array<uint32_t, 6> attrs{{app::R::attr::attr_one, app::R::attr::attr_two,
+ app::R::attr::attr_three, app::R::attr::attr_four,
+ app::R::attr::attr_five, app::R::attr::attr_empty}};
+ std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
+ std::array<uint32_t, attrs.size() + 1> indices;
+
+ while (state.KeepRunning()) {
+ ApplyStyle(theme.get(), &xml_tree, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(),
+ attrs.size(), values.data(), indices.data());
+ }
+}
+BENCHMARK(BM_ApplyStyle);
+
+static void BM_ApplyStyleFramework(benchmark::State& state) {
+ std::unique_ptr<const ApkAssets> framework_apk = ApkAssets::Load(kFrameworkPath);
+ if (framework_apk == nullptr) {
+ state.SkipWithError("failed to load framework assets");
+ return;
+ }
+
+ std::unique_ptr<const ApkAssets> basic_apk =
+ ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+ if (basic_apk == nullptr) {
+ state.SkipWithError("failed to load assets");
+ return;
+ }
+
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({framework_apk.get(), basic_apk.get()});
+
+ ResTable_config device_config;
+ memset(&device_config, 0, sizeof(device_config));
+ device_config.language[0] = 'e';
+ device_config.language[1] = 'n';
+ device_config.country[0] = 'U';
+ device_config.country[1] = 'S';
+ device_config.orientation = ResTable_config::ORIENTATION_PORT;
+ device_config.smallestScreenWidthDp = 700;
+ device_config.screenWidthDp = 700;
+ device_config.screenHeightDp = 1024;
+ device_config.sdkVersion = 27;
+
+ Res_value value;
+ ResTable_config config;
+ uint32_t flags = 0u;
+ ApkAssetsCookie cookie =
+ assetmanager.GetResource(basic::R::layout::layoutt, false /*may_be_bag*/,
+ 0u /*density_override*/, &value, &config, &flags);
+ if (cookie == kInvalidCookie) {
+ state.SkipWithError("failed to find R.layout.layout");
+ return;
+ }
+
+ size_t len = 0u;
+ const char* layout_path =
+ assetmanager.GetStringPoolForCookie(cookie)->string8At(value.data, &len);
+ if (layout_path == nullptr || len == 0u) {
+ state.SkipWithError("failed to lookup layout path");
+ return;
+ }
+
+ std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset(
+ StringPiece(layout_path, len).to_string(), cookie, Asset::ACCESS_BUFFER);
+ if (asset == nullptr) {
+ state.SkipWithError("failed to load layout");
+ return;
+ }
+
+ ResXMLTree xml_tree;
+ if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) {
+ state.SkipWithError("corrupt xml layout");
+ return;
+ }
+
+ // Skip to the first tag.
+ while (xml_tree.next() != ResXMLParser::START_TAG) {
+ }
+
+ std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+ theme->ApplyStyle(Theme_Material_Light);
+
+ std::array<uint32_t, 92> attrs{
+ {0x0101000e, 0x01010034, 0x01010095, 0x01010096, 0x01010097, 0x01010098, 0x01010099,
+ 0x0101009a, 0x0101009b, 0x010100ab, 0x010100af, 0x010100b0, 0x010100b1, 0x0101011f,
+ 0x01010120, 0x0101013f, 0x01010140, 0x0101014e, 0x0101014f, 0x01010150, 0x01010151,
+ 0x01010152, 0x01010153, 0x01010154, 0x01010155, 0x01010156, 0x01010157, 0x01010158,
+ 0x01010159, 0x0101015a, 0x0101015b, 0x0101015c, 0x0101015d, 0x0101015e, 0x0101015f,
+ 0x01010160, 0x01010161, 0x01010162, 0x01010163, 0x01010164, 0x01010165, 0x01010166,
+ 0x01010167, 0x01010168, 0x01010169, 0x0101016a, 0x0101016b, 0x0101016c, 0x0101016d,
+ 0x0101016e, 0x0101016f, 0x01010170, 0x01010171, 0x01010217, 0x01010218, 0x0101021d,
+ 0x01010220, 0x01010223, 0x01010224, 0x01010264, 0x01010265, 0x01010266, 0x010102c5,
+ 0x010102c6, 0x010102c7, 0x01010314, 0x01010315, 0x01010316, 0x0101035e, 0x0101035f,
+ 0x01010362, 0x01010374, 0x0101038c, 0x01010392, 0x01010393, 0x010103ac, 0x0101045d,
+ 0x010104b6, 0x010104b7, 0x010104d6, 0x010104d7, 0x010104dd, 0x010104de, 0x010104df,
+ 0x01010535, 0x01010536, 0x01010537, 0x01010538, 0x01010546, 0x01010567, 0x011100c9,
+ 0x011100ca}};
+
+ std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
+ std::array<uint32_t, attrs.size() + 1> indices;
+ while (state.KeepRunning()) {
+ ApplyStyle(theme.get(), &xml_tree, 0x01010084u /*def_style_attr*/, 0u /*def_style_res*/,
+ attrs.data(), attrs.size(), values.data(), indices.data());
+ }
+}
+BENCHMARK(BM_ApplyStyleFramework);
+
+} // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp
index 2d73ce8..cc30537 100644
--- a/libs/androidfw/tests/AttributeResolution_test.cpp
+++ b/libs/androidfw/tests/AttributeResolution_test.cpp
@@ -21,6 +21,7 @@
#include "android-base/file.h"
#include "android-base/logging.h"
#include "android-base/macros.h"
+#include "androidfw/AssetManager2.h"
#include "TestHelpers.h"
#include "data/styles/R.h"
@@ -32,15 +33,14 @@
class AttributeResolutionTest : public ::testing::Test {
public:
virtual void SetUp() override {
- std::string contents;
- ASSERT_TRUE(ReadFileFromZipToString(
- GetTestDataPath() + "/styles/styles.apk", "resources.arsc", &contents));
- ASSERT_EQ(NO_ERROR, table_.add(contents.data(), contents.size(),
- 1 /*cookie*/, true /*copyData*/));
+ styles_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+ ASSERT_NE(nullptr, styles_assets_);
+ assetmanager_.SetApkAssets({styles_assets_.get()});
}
protected:
- ResTable table_;
+ std::unique_ptr<const ApkAssets> styles_assets_;
+ AssetManager2 assetmanager_;
};
class AttributeResolutionXmlTest : public AttributeResolutionTest {
@@ -48,13 +48,12 @@
virtual void SetUp() override {
AttributeResolutionTest::SetUp();
- std::string contents;
- ASSERT_TRUE(
- ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk",
- "res/layout/layout.xml", &contents));
+ std::unique_ptr<Asset> asset =
+ assetmanager_.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER);
+ ASSERT_NE(nullptr, asset);
- ASSERT_EQ(NO_ERROR, xml_parser_.setTo(contents.data(), contents.size(),
- true /*copyData*/));
+ ASSERT_EQ(NO_ERROR,
+ xml_parser_.setTo(asset->getBuffer(true), asset->getLength(), true /*copyData*/));
// Skip to the first tag.
while (xml_parser_.next() != ResXMLParser::START_TAG) {
@@ -66,14 +65,14 @@
};
TEST_F(AttributeResolutionTest, Theme) {
- ResTable::Theme theme(table_);
- ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo));
+ std::unique_ptr<Theme> theme = assetmanager_.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo));
std::array<uint32_t, 5> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
R::attr::attr_four, R::attr::attr_empty}};
std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
- ASSERT_TRUE(ResolveAttrs(&theme, 0 /*def_style_attr*/, 0 /*def_style_res*/,
+ ASSERT_TRUE(ResolveAttrs(theme.get(), 0u /*def_style_attr*/, 0u /*def_style_res*/,
nullptr /*src_values*/, 0 /*src_values_length*/, attrs.data(),
attrs.size(), values.data(), nullptr /*out_indices*/));
@@ -126,8 +125,8 @@
R::attr::attr_four, R::attr::attr_empty}};
std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
- ASSERT_TRUE(RetrieveAttributes(&table_, &xml_parser_, attrs.data(), attrs.size(), values.data(),
- nullptr /*out_indices*/));
+ ASSERT_TRUE(RetrieveAttributes(&assetmanager_, &xml_parser_, attrs.data(), attrs.size(),
+ values.data(), nullptr /*out_indices*/));
uint32_t* values_cursor = values.data();
EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]);
@@ -171,15 +170,15 @@
}
TEST_F(AttributeResolutionXmlTest, ThemeAndXmlParser) {
- ResTable::Theme theme(table_);
- ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo));
+ std::unique_ptr<Theme> theme = assetmanager_.NewTheme();
+ ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo));
std::array<uint32_t, 6> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
R::attr::attr_four, R::attr::attr_five, R::attr::attr_empty}};
std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
std::array<uint32_t, attrs.size() + 1> indices;
- ApplyStyle(&theme, &xml_parser_, 0 /*def_style_attr*/, 0 /*def_style_res*/, attrs.data(),
+ ApplyStyle(theme.get(), &xml_parser_, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(),
attrs.size(), values.data(), indices.data());
const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC;
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index 7149bee..faddfe5 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -33,19 +33,21 @@
}
}
+ // Make sure to force creation of the ResTable first, or else the configuration doesn't get set.
+ const ResTable& table = assetmanager.getResources(true);
if (config != nullptr) {
assetmanager.setConfiguration(*config);
}
- const ResTable& table = assetmanager.getResources(true);
-
Res_value value;
ResTable_config selected_config;
uint32_t flags;
+ uint32_t last_ref = 0u;
while (state.KeepRunning()) {
- table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags,
- &selected_config);
+ ssize_t block = table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags,
+ &selected_config);
+ table.resolveReference(&value, block, &last_ref, &flags, &selected_config);
}
}
@@ -72,10 +74,12 @@
Res_value value;
ResTable_config selected_config;
uint32_t flags;
+ uint32_t last_id = 0u;
while (state.KeepRunning()) {
- assetmanager.GetResource(resid, false /* may_be_bag */, 0u /* density_override */, &value,
- &selected_config, &flags);
+ ApkAssetsCookie cookie = assetmanager.GetResource(
+ resid, false /* may_be_bag */, 0u /* density_override */, &value, &selected_config, &flags);
+ assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_id);
}
}
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 37ddafb..bedebd6 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -16,6 +16,8 @@
#include "androidfw/LoadedArsc.h"
+#include "androidfw/ResourceUtils.h"
+
#include "TestHelpers.h"
#include "data/basic/R.h"
#include "data/libclient/R.h"
@@ -27,6 +29,13 @@
namespace libclient = com::android::libclient;
namespace sparse = com::android::sparse;
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::IsNull;
+using ::testing::NotNull;
+using ::testing::SizeIs;
+using ::testing::StrEq;
+
namespace android {
TEST(LoadedArscTest, LoadSinglePackageArsc) {
@@ -35,39 +44,24 @@
&contents));
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
- const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
- ASSERT_EQ(1u, packages.size());
- EXPECT_EQ(std::string("com.android.app"), packages[0]->GetPackageName());
- EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(app::R::string::string_one));
+ ASSERT_THAT(package, NotNull());
+ EXPECT_THAT(package->GetPackageName(), StrEq("com.android.app"));
+ EXPECT_THAT(package->GetPackageId(), Eq(0x7f));
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 24;
+ const uint8_t type_index = get_type_id(app::R::string::string_one) - 1;
+ const uint16_t entry_index = get_entry_id(app::R::string::string_one);
- FindEntryResult entry;
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
- ASSERT_TRUE(loaded_arsc->FindEntry(app::R::string::string_one, config, &entry));
- ASSERT_NE(nullptr, entry.entry);
-}
-
-TEST(LoadedArscTest, FindDefaultEntry) {
- std::string contents;
- ASSERT_TRUE(
- ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
-
- std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
-
- ResTable_config desired_config;
- memset(&desired_config, 0, sizeof(desired_config));
- desired_config.language[0] = 'd';
- desired_config.language[1] = 'e';
-
- FindEntryResult entry;
- ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry));
- ASSERT_NE(nullptr, entry.entry);
+ const ResTable_type* type = type_spec->types[0];
+ ASSERT_THAT(type, NotNull());
+ ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
}
TEST(LoadedArscTest, LoadSparseEntryApp) {
@@ -76,15 +70,22 @@
&contents));
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
- ResTable_config config;
- memset(&config, 0, sizeof(config));
- config.sdkVersion = 26;
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
+ ASSERT_THAT(package, NotNull());
- FindEntryResult entry;
- ASSERT_TRUE(loaded_arsc->FindEntry(sparse::R::integer::foo_9, config, &entry));
- ASSERT_NE(nullptr, entry.entry);
+ const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
+ const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
+
+ const ResTable_type* type = type_spec->types[0];
+ ASSERT_THAT(type, NotNull());
+ ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
}
TEST(LoadedArscTest, LoadSharedLibrary) {
@@ -93,14 +94,13 @@
&contents));
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
const auto& packages = loaded_arsc->GetPackages();
- ASSERT_EQ(1u, packages.size());
-
+ ASSERT_THAT(packages, SizeIs(1u));
EXPECT_TRUE(packages[0]->IsDynamic());
- EXPECT_EQ(std::string("com.android.lib_one"), packages[0]->GetPackageName());
- EXPECT_EQ(0, packages[0]->GetPackageId());
+ EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.lib_one"));
+ EXPECT_THAT(packages[0]->GetPackageId(), Eq(0));
const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap();
@@ -114,25 +114,23 @@
"resources.arsc", &contents));
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
const auto& packages = loaded_arsc->GetPackages();
- ASSERT_EQ(1u, packages.size());
-
+ ASSERT_THAT(packages, SizeIs(1u));
EXPECT_FALSE(packages[0]->IsDynamic());
- EXPECT_EQ(std::string("com.android.libclient"), packages[0]->GetPackageName());
- EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+ EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.libclient"));
+ EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f));
const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap();
// The library has two dependencies.
- ASSERT_EQ(2u, dynamic_pkg_map.size());
+ ASSERT_THAT(dynamic_pkg_map, SizeIs(2u));
+ EXPECT_THAT(dynamic_pkg_map[0].package_name, StrEq("com.android.lib_one"));
+ EXPECT_THAT(dynamic_pkg_map[0].package_id, Eq(0x02));
- EXPECT_EQ(std::string("com.android.lib_one"), dynamic_pkg_map[0].package_name);
- EXPECT_EQ(0x02, dynamic_pkg_map[0].package_id);
-
- EXPECT_EQ(std::string("com.android.lib_two"), dynamic_pkg_map[1].package_name);
- EXPECT_EQ(0x03, dynamic_pkg_map[1].package_id);
+ EXPECT_THAT(dynamic_pkg_map[1].package_name, StrEq("com.android.lib_two"));
+ EXPECT_THAT(dynamic_pkg_map[1].package_id, Eq(0x03));
}
TEST(LoadedArscTest, LoadAppAsSharedLibrary) {
@@ -143,13 +141,12 @@
std::unique_ptr<const LoadedArsc> loaded_arsc =
LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/,
true /*load_as_shared_library*/);
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
const auto& packages = loaded_arsc->GetPackages();
- ASSERT_EQ(1u, packages.size());
-
+ ASSERT_THAT(packages, SizeIs(1u));
EXPECT_TRUE(packages[0]->IsDynamic());
- EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+ EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f));
}
TEST(LoadedArscTest, LoadFeatureSplit) {
@@ -157,21 +154,27 @@
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/feature/feature.apk", "resources.arsc",
&contents));
std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
- ASSERT_NE(nullptr, loaded_arsc);
+ ASSERT_THAT(loaded_arsc, NotNull());
- ResTable_config desired_config;
- memset(&desired_config, 0, sizeof(desired_config));
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(basic::R::string::test3));
+ ASSERT_THAT(package, NotNull());
- FindEntryResult entry;
- ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry));
+ uint8_t type_index = get_type_id(basic::R::string::test3) - 1;
+ uint8_t entry_index = get_entry_id(basic::R::string::test3);
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
+ ASSERT_THAT(type_spec->types[0], NotNull());
size_t len;
- const char16_t* type_name16 = entry.type_string_ref.string16(&len);
- ASSERT_NE(nullptr, type_name16);
- ASSERT_NE(0u, len);
+ const char16_t* type_name16 =
+ package->GetTypeStringPool()->stringAt(type_spec->type_spec->id - 1, &len);
+ ASSERT_THAT(type_name16, NotNull());
+ EXPECT_THAT(util::Utf16ToUtf8(StringPiece16(type_name16, len)), StrEq("string"));
- std::string type_name = util::Utf16ToUtf8(StringPiece16(type_name16, len));
- EXPECT_EQ(std::string("string"), type_name);
+ ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], entry_index), NotNull());
}
class MockLoadedIdmap : public LoadedIdmap {
@@ -199,23 +202,33 @@
};
TEST(LoadedArscTest, LoadOverlay) {
- std::string contents, overlay_contents;
- ASSERT_TRUE(
- ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
+ std::string contents;
ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk", "resources.arsc",
- &overlay_contents));
+ &contents));
MockLoadedIdmap loaded_idmap;
std::unique_ptr<const LoadedArsc> loaded_arsc =
- LoadedArsc::Load(StringPiece(overlay_contents), &loaded_idmap);
- ASSERT_NE(nullptr, loaded_arsc);
+ LoadedArsc::Load(StringPiece(contents), &loaded_idmap);
+ ASSERT_THAT(loaded_arsc, NotNull());
- ResTable_config desired_config;
- memset(&desired_config, 0, sizeof(desired_config));
+ const LoadedPackage* package = loaded_arsc->GetPackageById(0x08u);
+ ASSERT_THAT(package, NotNull());
- FindEntryResult entry;
- ASSERT_TRUE(loaded_arsc->FindEntry(0x08030001u, desired_config, &entry));
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0x03u - 1);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
+ ASSERT_THAT(type_spec->types[0], NotNull());
+
+ // The entry being overlaid doesn't exist at the original entry index.
+ ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0001u), IsNull());
+
+ // Since this is an overlay, the actual entry ID must be mapped.
+ ASSERT_THAT(type_spec->idmap_entries, NotNull());
+ uint16_t target_entry_id = 0u;
+ ASSERT_TRUE(LoadedIdmap::Lookup(type_spec->idmap_entries, 0x0001u, &target_entry_id));
+ ASSERT_THAT(target_entry_id, Eq(0x0u));
+ ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0000), NotNull());
}
// structs with size fields (like Res_value, ResTable_entry) should be
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
index 43a9955..df0c642 100644
--- a/libs/androidfw/tests/TestHelpers.h
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -20,6 +20,7 @@
#include <string>
#include "androidfw/ResourceTypes.h"
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "CommonHelpers.h"
diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h
index 94a2a14..b7e814f 100644
--- a/libs/androidfw/tests/data/basic/R.h
+++ b/libs/androidfw/tests/data/basic/R.h
@@ -34,6 +34,7 @@
struct layout {
enum : uint32_t {
main = 0x7f020000,
+ layoutt = 0x7f020001,
};
};
@@ -55,6 +56,7 @@
number2 = 0x7f040001,
ref1 = 0x7f040002,
ref2 = 0x7f040003,
+ deep_ref = 0x7f040004,
// From feature
number3 = 0x80030000,
diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk
index 18ef75e..1733b6a 100644
--- a/libs/androidfw/tests/data/basic/basic.apk
+++ b/libs/androidfw/tests/data/basic/basic.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/res/layout/layout.xml b/libs/androidfw/tests/data/basic/res/layout/layout.xml
new file mode 100644
index 0000000..045ede4
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/layout/layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/ok"
+ android:layout_width="0sp"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:layout_marginStart="2dip"
+ android:layout_marginEnd="2dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:text="@android:string/ok" />
\ No newline at end of file
diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml
index 6c47459..b343562 100644
--- a/libs/androidfw/tests/data/basic/res/values/values.xml
+++ b/libs/androidfw/tests/data/basic/res/values/values.xml
@@ -22,6 +22,7 @@
<attr name="attr2" format="reference|integer" />
<public type="layout" name="main" id="0x7f020000" />
+ <public type="layout" name="layout" id="0x7f020001" />
<public type="string" name="test1" id="0x7f030000" />
<string name="test1">test1</string>
@@ -43,6 +44,18 @@
<public type="integer" name="ref2" id="0x7f040003" />
<integer name="ref2">12000</integer>
+ <public type="integer" name="deep_ref" id="0x7f040004" />
+ <integer name="deep_ref">@integer/deep_ref_1</integer>
+ <integer name="deep_ref_1">@integer/deep_ref_2</integer>
+ <integer name="deep_ref_2">@integer/deep_ref_3</integer>
+ <integer name="deep_ref_3">@integer/deep_ref_4</integer>
+ <integer name="deep_ref_4">@integer/deep_ref_5</integer>
+ <integer name="deep_ref_5">@integer/deep_ref_6</integer>
+ <integer name="deep_ref_6">@integer/deep_ref_7</integer>
+ <integer name="deep_ref_7">@integer/deep_ref_8</integer>
+ <integer name="deep_ref_8">@integer/deep_ref_9</integer>
+ <integer name="deep_ref_9">100</integer>
+
<public type="style" name="Theme1" id="0x7f050000" />
<style name="Theme1">
<item name="com.android.basic:attr1">100</item>
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 4243e7e..6cd283a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -61,6 +61,8 @@
bool Properties::skpCaptureEnabled = false;
bool Properties::enableRTAnimations = true;
+bool Properties::runningInEmulator = false;
+
static int property_get_int(const char* key, int defaultValue) {
char buf[PROPERTY_VALUE_MAX] = {
'\0',
@@ -135,6 +137,8 @@
skpCaptureEnabled = property_get_bool("ro.debuggable", false) &&
property_get_bool(PROPERTY_CAPTURE_SKP_ENABLED, false);
+ runningInEmulator = property_get_bool(PROPERTY_QEMU_KERNEL, false);
+
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw) ||
(prevDebugStencilClip != debugStencilClip);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index af4b694..179b97b 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -180,6 +180,11 @@
*/
#define PROPERTY_CAPTURE_SKP_FILENAME "debug.hwui.skp_filename"
+/**
+ * Property for whether this is running in the emulator.
+ */
+#define PROPERTY_QEMU_KERNEL "ro.kernel.qemu"
+
///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
@@ -261,6 +266,8 @@
// Used for testing only to change the render pipeline.
static void overrideRenderPipelineType(RenderPipelineType);
+ static bool runningInEmulator;
+
private:
static ProfileType sProfileType;
static bool sDisableProfileBars;
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 36dd06f..e01bf3d 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -36,18 +36,27 @@
mColorFilter = mStagingColorFilter;
}
-void AnimatedImageDrawable::start() {
+bool AnimatedImageDrawable::start() {
SkAutoExclusive lock(mLock);
+ if (mSkAnimatedImage->isRunning()) {
+ return false;
+ }
- mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
+ if (!mSnapshot) {
+ mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
+ }
+ // While stopped, update() does not decode, but it does advance the time.
+ // This prevents us from skipping ahead when we resume.
+ const double currentTime = SkTime::GetMSecs();
+ mSkAnimatedImage->update(currentTime);
mSkAnimatedImage->start();
+ return mSkAnimatedImage->isRunning();
}
void AnimatedImageDrawable::stop() {
SkAutoExclusive lock(mLock);
mSkAnimatedImage->stop();
- mSnapshot.reset(nullptr);
}
bool AnimatedImageDrawable::isRunning() {
@@ -120,7 +129,7 @@
}
SkAutoExclusive lock(mLock);
- if (mSkAnimatedImage->isRunning()) {
+ if (mSnapshot) {
canvas->drawPicture(mSnapshot, nullptr, lazyPaint.getMaybeNull());
} else {
// TODO: we could potentially keep the cached surface around if there is a paint and we know
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index 18764af..1ebb585 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -58,7 +58,8 @@
double drawStaging(SkCanvas* canvas);
- void start();
+ // Returns true if the animation was started; false otherwise (e.g. it was already running)
+ bool start();
void stop();
bool isRunning();
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index 75e414e..284fd83 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -158,12 +158,13 @@
void Canvas::drawText(const uint16_t* text, int start, int count, int contextCount, float x,
float y, minikin::Bidi bidiFlags, const Paint& origPaint,
- const Typeface* typeface) {
+ const Typeface* typeface, minikin::MeasuredText* mt, int mtOffset) {
// minikin may modify the original paint
Paint paint(origPaint);
minikin::Layout layout =
- MinikinUtils::doLayout(&paint, bidiFlags, typeface, text, start, count, contextCount);
+ MinikinUtils::doLayout(&paint, bidiFlags, typeface, text, start, count, contextCount,
+ mt, mtOffset);
x += MinikinUtils::xOffsetForTextAlign(&paint, layout);
@@ -211,7 +212,8 @@
const Typeface* typeface) {
Paint paintCopy(paint);
minikin::Layout layout =
- MinikinUtils::doLayout(&paintCopy, bidiFlags, typeface, text, 0, count, count);
+ MinikinUtils::doLayout(&paintCopy, bidiFlags, typeface, text, 0, count, count, nullptr,
+ 0);
hOffset += MinikinUtils::hOffsetForTextAlign(&paintCopy, layout, path);
// Set align to left for drawing, as we don't want individual
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index cae4542..3ddf1c4 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -34,6 +34,7 @@
namespace minikin {
class Layout;
+class MeasuredText;
enum class Bidi : uint8_t;
}
@@ -260,7 +261,8 @@
* and delegating the final draw to virtual drawGlyphs method.
*/
void drawText(const uint16_t* text, int start, int count, int contextCount, float x, float y,
- minikin::Bidi bidiFlags, const Paint& origPaint, const Typeface* typeface);
+ minikin::Bidi bidiFlags, const Paint& origPaint, const Typeface* typeface,
+ minikin::MeasuredText* mt, int mtOffset);
void drawTextOnPath(const uint16_t* text, int count, minikin::Bidi bidiFlags,
const SkPath& path, float hOffset, float vOffset, const Paint& paint,
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index bad766c..ba877d3 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -20,6 +20,7 @@
#include <log/log.h>
+#include <minikin/MeasuredText.h>
#include "Paint.h"
#include "SkPathMeasure.h"
#include "Typeface.h"
@@ -49,11 +50,24 @@
minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf, size_t start,
- size_t count, size_t bufSize) {
+ size_t count, size_t bufSize, minikin::MeasuredText* mt,
+ int mtOffset) {
minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
+ const auto& fc = Typeface::resolveDefault(typeface)->fFontCollection;
minikin::Layout layout;
- layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint,
- Typeface::resolveDefault(typeface)->fFontCollection);
+
+ if (mt == nullptr) {
+ layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint, fc);
+ return layout;
+ }
+
+ if (mt->buildLayout(minikin::U16StringPiece(buf, bufSize),
+ minikin::Range(start, start + count),
+ minikinPaint, fc, bidiFlags, mtOffset, &layout)) {
+ return layout;
+ }
+
+ layout.doLayout(buf, start, count, bufSize, bidiFlags, minikinPaint, fc);
return layout;
}
@@ -64,7 +78,7 @@
const Typeface* resolvedTypeface = Typeface::resolveDefault(typeface);
return minikin::Layout::measureText(buf, start, count, bufSize, bidiFlags, minikinPaint,
resolvedTypeface->fFontCollection, advances,
- nullptr /* extent */, nullptr /* overhangs */);
+ nullptr /* extent */);
}
bool MinikinUtils::hasVariationSelector(const Typeface* typeface, uint32_t codepoint, uint32_t vs) {
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 7036cbe..124fe4f 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -29,6 +29,11 @@
#include "MinikinSkia.h"
#include "Paint.h"
#include "Typeface.h"
+#include <log/log.h>
+
+namespace minikin {
+class MeasuredText;
+} // namespace minikin
namespace android {
@@ -39,7 +44,8 @@
ANDROID_API static minikin::Layout doLayout(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf,
- size_t start, size_t count, size_t bufSize);
+ size_t start, size_t count, size_t bufSize,
+ minikin::MeasuredText* mt, int mtOffset);
ANDROID_API static float measureText(const Paint* paint, minikin::Bidi bidiFlags,
const Typeface* typeface, const uint16_t* buf,
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index c7a3014..2fa56f6 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -19,6 +19,7 @@
#include <log/log.h>
#include <thread>
#include "FileBlobCache.h"
+#include "Properties.h"
#include "utils/TraceUtils.h"
namespace android {
@@ -43,7 +44,11 @@
void ShaderCache::initShaderDiskCache() {
ATRACE_NAME("initShaderDiskCache");
std::lock_guard<std::mutex> lock(mMutex);
- if (mFilename.length() > 0) {
+
+ // Emulators can switch between different renders either as part of config
+ // or snapshot migration. Also, program binaries may not work well on some
+ // desktop / laptop GPUs. Thus, disable the shader disk cache for emulator builds.
+ if (!Properties::runningInEmulator && mFilename.length() > 0) {
mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
mInitialized = true;
}
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index 4a0d6ee..51cf772 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -125,7 +125,7 @@
SkPaint glyphPaint(paint);
glyphPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
canvas->drawText(utf16.get(), 0, strlen(text), strlen(text), x, y, minikin::Bidi::LTR,
- glyphPaint, nullptr);
+ glyphPaint, nullptr, nullptr /* measured text */, 0 /* measured text offset */);
}
void TestUtils::drawUtf8ToCanvas(Canvas* canvas, const char* text, const SkPaint& paint,
diff --git a/libs/services/Android.bp b/libs/services/Android.bp
index e5e865f..3d57fbd 100644
--- a/libs/services/Android.bp
+++ b/libs/services/Android.bp
@@ -19,6 +19,7 @@
srcs: [
":IDropBoxManagerService.aidl",
"src/os/DropBoxManager.cpp",
+ "src/os/StatsDimensionsValue.cpp",
"src/os/StatsLogEventWrapper.cpp",
],
diff --git a/libs/services/include/android/os/DropBoxManager.h b/libs/services/include/android/os/DropBoxManager.h
index 2ed203d..75b26c6 100644
--- a/libs/services/include/android/os/DropBoxManager.h
+++ b/libs/services/include/android/os/DropBoxManager.h
@@ -58,6 +58,10 @@
// are required from the system process. Returns NULL if the file can't be opened.
Status addFile(const String16& tag, const string& filename, int flags);
+ // Create a new Entry from an already opened file. Takes ownership of the
+ // file descriptor.
+ Status addFile(const String16& tag, int fd, int flags);
+
class Entry : public virtual RefBase, public Parcelable {
public:
Entry();
diff --git a/libs/services/include/android/os/StatsDimensionsValue.h b/libs/services/include/android/os/StatsDimensionsValue.h
new file mode 100644
index 0000000..cc0b056
--- /dev/null
+++ b/libs/services/include/android/os/StatsDimensionsValue.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+#ifndef STATS_DIMENSIONS_VALUE_H
+#define STATS_DIMENSIONS_VALUE_H
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/Status.h>
+#include <utils/String16.h>
+#include <vector>
+
+namespace android {
+namespace os {
+
+// Represents a parcelable object. Used to send data from statsd to StatsCompanionService.java.
+class StatsDimensionsValue : public android::Parcelable {
+public:
+ StatsDimensionsValue();
+
+ StatsDimensionsValue(int32_t field, String16 value);
+ StatsDimensionsValue(int32_t field, int32_t value);
+ StatsDimensionsValue(int32_t field, int64_t value);
+ StatsDimensionsValue(int32_t field, bool value);
+ StatsDimensionsValue(int32_t field, float value);
+ StatsDimensionsValue(int32_t field, std::vector<StatsDimensionsValue> value);
+
+ virtual ~StatsDimensionsValue();
+
+ virtual android::status_t writeToParcel(android::Parcel* out) const override;
+ virtual android::status_t readFromParcel(const android::Parcel* in) override;
+
+private:
+ // Keep constants in sync with android/os/StatsDimensionsValue.java
+ // and stats_log.proto's DimensionValue.
+ static const int kStrValueType = 2;
+ static const int kIntValueType = 3;
+ static const int kLongValueType = 4;
+ static const int kBoolValueType = 5;
+ static const int kFloatValueType = 6;
+ static const int kTupleValueType = 7;
+
+ int32_t mField;
+ int32_t mValueType;
+
+ // This isn't very clever, but it isn't used for long-term storage, so it'll do.
+ String16 mStrValue;
+ int32_t mIntValue;
+ int64_t mLongValue;
+ bool mBoolValue;
+ float mFloatValue;
+ std::vector<StatsDimensionsValue> mTupleValue;
+};
+
+} // namespace os
+} // namespace android
+
+#endif // STATS_DIMENSIONS_VALUE_H
diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp
index 1c760e8..f5685d9 100644
--- a/libs/services/src/os/DropBoxManager.cpp
+++ b/libs/services/src/os/DropBoxManager.cpp
@@ -202,7 +202,12 @@
ALOGW("DropboxManager: %s", message.c_str());
return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, message.c_str());
}
+ return addFile(tag, fd, flags);
+}
+Status
+DropBoxManager::addFile(const String16& tag, int fd, int flags)
+{
Entry entry(tag, flags, fd);
return add(entry);
}
diff --git a/libs/services/src/os/StatsDimensionsValue.cpp b/libs/services/src/os/StatsDimensionsValue.cpp
new file mode 100644
index 0000000..0052e0b
--- /dev/null
+++ b/libs/services/src/os/StatsDimensionsValue.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "StatsDimensionsValue"
+
+#include "android/os/StatsDimensionsValue.h"
+
+#include <cutils/log.h>
+
+using android::Parcel;
+using android::Parcelable;
+using android::status_t;
+using std::vector;
+
+namespace android {
+namespace os {
+
+StatsDimensionsValue::StatsDimensionsValue() {};
+
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, String16 value) :
+ mField(field),
+ mValueType(kStrValueType),
+ mStrValue(value) {
+}
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, int32_t value) :
+ mField(field),
+ mValueType(kIntValueType),
+ mIntValue(value) {
+}
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, int64_t value) :
+ mField(field),
+ mValueType(kLongValueType),
+ mLongValue(value) {
+}
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, bool value) :
+ mField(field),
+ mValueType(kBoolValueType),
+ mBoolValue(value) {
+}
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, float value) :
+ mField(field),
+ mValueType(kFloatValueType),
+ mFloatValue(value) {
+}
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, vector<StatsDimensionsValue> value) :
+ mField(field),
+ mValueType(kTupleValueType),
+ mTupleValue(value) {
+}
+
+StatsDimensionsValue::~StatsDimensionsValue() {}
+
+status_t
+StatsDimensionsValue::writeToParcel(Parcel* out) const {
+ status_t err ;
+
+ err = out->writeInt32(mField);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ err = out->writeInt32(mValueType);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ switch (mValueType) {
+ case kStrValueType:
+ err = out->writeString16(mStrValue);
+ break;
+ case kIntValueType:
+ err = out->writeInt32(mIntValue);
+ break;
+ case kLongValueType:
+ err = out->writeInt64(mLongValue);
+ break;
+ case kBoolValueType:
+ err = out->writeBool(mBoolValue);
+ break;
+ case kFloatValueType:
+ err = out->writeFloat(mFloatValue);
+ break;
+ case kTupleValueType:
+ {
+ int sz = mTupleValue.size();
+ err = out->writeInt32(sz);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ for (int i = 0; i < sz; ++i) {
+ err = mTupleValue[i].writeToParcel(out);
+ if (err != NO_ERROR) {
+ return err;
+ }
+ }
+ }
+ break;
+ default:
+ err = UNKNOWN_ERROR;
+ break;
+ }
+ return err;
+}
+
+status_t
+StatsDimensionsValue::readFromParcel(const Parcel* in)
+{
+ // Implement me if desired. We don't currently use this.
+ ALOGE("Cannot do c++ StatsDimensionsValue.readFromParcel(); it is not implemented.");
+ (void)in; // To prevent compile error of unused parameter 'in'
+ return UNKNOWN_ERROR;
+}
+
+} // namespace os
+} // namespace android
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 27784e9..eb6e830 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -32,6 +32,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArrayMap;
@@ -1314,6 +1315,23 @@
return native_read_in_direct_buffer(audioBuffer, sizeInBytes, readMode == READ_BLOCKING);
}
+ /**
+ * Return Metrics data about the current AudioTrack instance.
+ *
+ * @return a {@link PersistableBundle} containing the set of attributes and values
+ * available for the media being handled by this instance of AudioRecord
+ * The attributes are descibed in {@link MetricsConstants}.
+ *
+ * Additional vendor-specific fields may also be present in
+ * the return value.
+ */
+ public PersistableBundle getMetrics() {
+ PersistableBundle bundle = native_getMetrics();
+ return bundle;
+ }
+
+ private native PersistableBundle native_getMetrics();
+
//--------------------------------------------------------------------------
// Initialization / configuration
//--------------------
@@ -1739,4 +1757,46 @@
private static void loge(String msg) {
Log.e(TAG, msg);
}
+
+ public static final class MetricsConstants
+ {
+ private MetricsConstants() {}
+
+ /**
+ * Key to extract the output format being recorded
+ * from the {@link AudioRecord#getMetrics} return value.
+ * The value is a String.
+ */
+ public static final String ENCODING = "android.media.audiorecord.encoding";
+
+ /**
+ * Key to extract the Source Type for this track
+ * from the {@link AudioRecord#getMetrics} return value.
+ * The value is a String.
+ */
+ public static final String SOURCE = "android.media.audiorecord.source";
+
+ /**
+ * Key to extract the estimated latency through the recording pipeline
+ * from the {@link AudioRecord#getMetrics} return value.
+ * This is in units of milliseconds.
+ * The value is an integer.
+ */
+ public static final String LATENCY = "android.media.audiorecord.latency";
+
+ /**
+ * Key to extract the sink sample rate for this record track in Hz
+ * from the {@link AudioRecord#getMetrics} return value.
+ * The value is an integer.
+ */
+ public static final String SAMPLERATE = "android.media.audiorecord.samplerate";
+
+ /**
+ * Key to extract the number of channels being recorded in this record track
+ * from the {@link AudioRecord#getMetrics} return value.
+ * The value is an integer.
+ */
+ public static final String CHANNELS = "android.media.audiorecord.channels";
+
+ }
}
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 5928d03..4e9ce8e 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -36,6 +36,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -1718,6 +1719,23 @@
return ret;
}
+ /**
+ * Return Metrics data about the current AudioTrack instance.
+ *
+ * @return a {@link PersistableBundle} containing the set of attributes and values
+ * available for the media being handled by this instance of AudioTrack
+ * The attributes are descibed in {@link MetricsConstants}.
+ *
+ * Additional vendor-specific fields may also be present in
+ * the return value.
+ */
+ public PersistableBundle getMetrics() {
+ PersistableBundle bundle = native_getMetrics();
+ return bundle;
+ }
+
+ private native PersistableBundle native_getMetrics();
+
//--------------------------------------------------------------------------
// Initialization / configuration
//--------------------
@@ -3239,4 +3257,46 @@
private static void loge(String msg) {
Log.e(TAG, msg);
}
+
+ public final static class MetricsConstants
+ {
+ private MetricsConstants() {}
+
+ /**
+ * Key to extract the Stream Type for this track
+ * from the {@link AudioTrack#getMetrics} return value.
+ * The value is a String.
+ */
+ public static final String STREAMTYPE = "android.media.audiotrack.streamtype";
+
+ /**
+ * Key to extract the Content Type for this track
+ * from the {@link AudioTrack#getMetrics} return value.
+ * The value is a String.
+ */
+ public static final String CONTENTTYPE = "android.media.audiotrack.type";
+
+ /**
+ * Key to extract the Content Type for this track
+ * from the {@link AudioTrack#getMetrics} return value.
+ * The value is a String.
+ */
+ public static final String USAGE = "android.media.audiotrack.usage";
+
+ /**
+ * Key to extract the sample rate for this track in Hz
+ * from the {@link AudioTrack#getMetrics} return value.
+ * The value is an integer.
+ */
+ public static final String SAMPLERATE = "android.media.audiorecord.samplerate";
+
+ /**
+ * Key to extract the channel mask information for this track
+ * from the {@link AudioTrack#getMetrics} return value.
+ *
+ * The value is a Long integer.
+ */
+ public static final String CHANNELMASK = "android.media.audiorecord.channelmask";
+
+ }
}
diff --git a/media/java/android/media/DataSourceDesc.java b/media/java/android/media/DataSourceDesc.java
new file mode 100644
index 0000000..73fad7a
--- /dev/null
+++ b/media/java/android/media/DataSourceDesc.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright 2018 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.HttpCookie;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Structure for data source descriptor.
+ *
+ * Used by {@link MediaPlayer2#setDataSource(DataSourceDesc)}
+ * to set data source for playback.
+ *
+ * <p>Users should use {@link Builder} to change {@link DataSourceDesc}.
+ *
+ */
+public final class DataSourceDesc {
+ /* No data source has been set yet */
+ public static final int TYPE_NONE = 0;
+ /* data source is type of MediaDataSource */
+ public static final int TYPE_CALLBACK = 1;
+ /* data source is type of FileDescriptor */
+ public static final int TYPE_FD = 2;
+ /* data source is type of Uri */
+ public static final int TYPE_URI = 3;
+
+ // intentionally less than long.MAX_VALUE
+ public static final long LONG_MAX = 0x7ffffffffffffffL;
+
+ private int mType = TYPE_NONE;
+
+ private Media2DataSource mMedia2DataSource;
+
+ private FileDescriptor mFD;
+ private long mFDOffset = 0;
+ private long mFDLength = LONG_MAX;
+
+ private Uri mUri;
+ private Map<String, String> mUriHeader;
+ private List<HttpCookie> mUriCookies;
+ private Context mUriContext;
+
+ private long mId = 0;
+ private long mStartPositionMs = 0;
+ private long mEndPositionMs = LONG_MAX;
+
+ private DataSourceDesc() {
+ }
+
+ /**
+ * Return the Id of data source.
+ * @return the Id of data source
+ */
+ public long getId() {
+ return mId;
+ }
+
+ /**
+ * Return the position in milliseconds at which the playback will start.
+ * @return the position in milliseconds at which the playback will start
+ */
+ public long getStartPosition() {
+ return mStartPositionMs;
+ }
+
+ /**
+ * Return the position in milliseconds at which the playback will end.
+ * -1 means ending at the end of source content.
+ * @return the position in milliseconds at which the playback will end
+ */
+ public long getEndPosition() {
+ return mEndPositionMs;
+ }
+
+ /**
+ * Return the type of data source.
+ * @return the type of data source
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Return the Media2DataSource of this data source.
+ * It's meaningful only when {@code getType} returns {@link #TYPE_CALLBACK}.
+ * @return the Media2DataSource of this data source
+ */
+ public Media2DataSource getMedia2DataSource() {
+ return mMedia2DataSource;
+ }
+
+ /**
+ * Return the FileDescriptor of this data source.
+ * It's meaningful only when {@code getType} returns {@link #TYPE_FD}.
+ * @return the FileDescriptor of this data source
+ */
+ public FileDescriptor getFileDescriptor() {
+ return mFD;
+ }
+
+ /**
+ * Return the offset associated with the FileDescriptor of this data source.
+ * It's meaningful only when {@code getType} returns {@link #TYPE_FD} and it has
+ * been set by the {@link Builder}.
+ * @return the offset associated with the FileDescriptor of this data source
+ */
+ public long getFileDescriptorOffset() {
+ return mFDOffset;
+ }
+
+ /**
+ * Return the content length associated with the FileDescriptor of this data source.
+ * It's meaningful only when {@code getType} returns {@link #TYPE_FD}.
+ * -1 means same as the length of source content.
+ * @return the content length associated with the FileDescriptor of this data source
+ */
+ public long getFileDescriptorLength() {
+ return mFDLength;
+ }
+
+ /**
+ * Return the Uri of this data source.
+ * It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
+ * @return the Uri of this data source
+ */
+ public Uri getUri() {
+ return mUri;
+ }
+
+ /**
+ * Return the Uri headers of this data source.
+ * It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
+ * @return the Uri headers of this data source
+ */
+ public Map<String, String> getUriHeaders() {
+ if (mUriHeader == null) {
+ return null;
+ }
+ return new HashMap<String, String>(mUriHeader);
+ }
+
+ /**
+ * Return the Uri cookies of this data source.
+ * It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
+ * @return the Uri cookies of this data source
+ */
+ public List<HttpCookie> getUriCookies() {
+ if (mUriCookies == null) {
+ return null;
+ }
+ return new ArrayList<HttpCookie>(mUriCookies);
+ }
+
+ /**
+ * Return the Context used for resolving the Uri of this data source.
+ * It's meaningful only when {@code getType} returns {@link #TYPE_URI}.
+ * @return the Context used for resolving the Uri of this data source
+ */
+ public Context getUriContext() {
+ return mUriContext;
+ }
+
+ /**
+ * Builder class for {@link DataSourceDesc} objects.
+ * <p> Here is an example where <code>Builder</code> is used to define the
+ * {@link DataSourceDesc} to be used by a {@link MediaPlayer2} instance:
+ *
+ * <pre class="prettyprint">
+ * DataSourceDesc oldDSD = mediaplayer2.getDataSourceDesc();
+ * DataSourceDesc newDSD = new DataSourceDesc.Builder(oldDSD)
+ * .setStartPosition(1000)
+ * .setEndPosition(15000)
+ * .build();
+ * mediaplayer2.setDataSourceDesc(newDSD);
+ * </pre>
+ */
+ public static class Builder {
+ private int mType = TYPE_NONE;
+
+ private Media2DataSource mMedia2DataSource;
+
+ private FileDescriptor mFD;
+ private long mFDOffset = 0;
+ private long mFDLength = LONG_MAX;
+
+ private Uri mUri;
+ private Map<String, String> mUriHeader;
+ private List<HttpCookie> mUriCookies;
+ private Context mUriContext;
+
+ private long mId = 0;
+ private long mStartPositionMs = 0;
+ private long mEndPositionMs = LONG_MAX;
+
+ /**
+ * Constructs a new Builder with the defaults.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Constructs a new Builder from a given {@link DataSourceDesc} instance
+ * @param dsd the {@link DataSourceDesc} object whose data will be reused
+ * in the new Builder.
+ */
+ public Builder(DataSourceDesc dsd) {
+ mType = dsd.mType;
+ mMedia2DataSource = dsd.mMedia2DataSource;
+ mFD = dsd.mFD;
+ mFDOffset = dsd.mFDOffset;
+ mFDLength = dsd.mFDLength;
+ mUri = dsd.mUri;
+ mUriHeader = dsd.mUriHeader;
+ mUriCookies = dsd.mUriCookies;
+ mUriContext = dsd.mUriContext;
+
+ mId = dsd.mId;
+ mStartPositionMs = dsd.mStartPositionMs;
+ mEndPositionMs = dsd.mEndPositionMs;
+ }
+
+ /**
+ * Combines all of the fields that have been set and return a new
+ * {@link DataSourceDesc} object. <code>IllegalStateException</code> will be
+ * thrown if there is conflict between fields.
+ *
+ * @return a new {@link DataSourceDesc} object
+ */
+ public DataSourceDesc build() {
+ if (mType != TYPE_CALLBACK
+ && mType != TYPE_FD
+ && mType != TYPE_URI) {
+ throw new IllegalStateException("Illegal type: " + mType);
+ }
+ if (mStartPositionMs > mEndPositionMs) {
+ throw new IllegalStateException("Illegal start/end position: "
+ + mStartPositionMs + " : " + mEndPositionMs);
+ }
+
+ DataSourceDesc dsd = new DataSourceDesc();
+ dsd.mType = mType;
+ dsd.mMedia2DataSource = mMedia2DataSource;
+ dsd.mFD = mFD;
+ dsd.mFDOffset = mFDOffset;
+ dsd.mFDLength = mFDLength;
+ dsd.mUri = mUri;
+ dsd.mUriHeader = mUriHeader;
+ dsd.mUriCookies = mUriCookies;
+ dsd.mUriContext = mUriContext;
+
+ dsd.mId = mId;
+ dsd.mStartPositionMs = mStartPositionMs;
+ dsd.mEndPositionMs = mEndPositionMs;
+
+ return dsd;
+ }
+
+ /**
+ * Sets the Id of this data source.
+ *
+ * @param id the Id of this data source
+ * @return the same Builder instance.
+ */
+ public Builder setId(long id) {
+ mId = id;
+ return this;
+ }
+
+ /**
+ * Sets the start position in milliseconds at which the playback will start.
+ * Any negative number is treated as 0.
+ *
+ * @param position the start position in milliseconds at which the playback will start
+ * @return the same Builder instance.
+ *
+ */
+ public Builder setStartPosition(long position) {
+ if (position < 0) {
+ position = 0;
+ }
+ mStartPositionMs = position;
+ return this;
+ }
+
+ /**
+ * Sets the end position in milliseconds at which the playback will end.
+ * Any negative number is treated as maximum length of the data source.
+ *
+ * @param position the end position in milliseconds at which the playback will end
+ * @return the same Builder instance.
+ */
+ public Builder setEndPosition(long position) {
+ if (position < 0) {
+ position = LONG_MAX;
+ }
+ mEndPositionMs = position;
+ return this;
+ }
+
+ /**
+ * Sets the data source (Media2DataSource) to use.
+ *
+ * @param m2ds the Media2DataSource for the media you want to play
+ * @return the same Builder instance.
+ * @throws NullPointerException if m2ds is null.
+ */
+ public Builder setDataSource(Media2DataSource m2ds) {
+ Preconditions.checkNotNull(m2ds);
+ resetDataSource();
+ mType = TYPE_CALLBACK;
+ mMedia2DataSource = m2ds;
+ return this;
+ }
+
+ /**
+ * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
+ * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
+ * to close the file descriptor after the source has been used.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @return the same Builder instance.
+ * @throws NullPointerException if fd is null.
+ */
+ public Builder setDataSource(FileDescriptor fd) {
+ Preconditions.checkNotNull(fd);
+ resetDataSource();
+ mType = TYPE_FD;
+ mFD = fd;
+ return this;
+ }
+
+ /**
+ * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
+ * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
+ * to close the file descriptor after the source has been used.
+ *
+ * Any negative number for offset is treated as 0.
+ * Any negative number for length is treated as maximum length of the data source.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @param offset the offset into the file where the data to be played starts, in bytes
+ * @param length the length in bytes of the data to be played
+ * @return the same Builder instance.
+ * @throws NullPointerException if fd is null.
+ */
+ public Builder setDataSource(FileDescriptor fd, long offset, long length) {
+ Preconditions.checkNotNull(fd);
+ if (offset < 0) {
+ offset = 0;
+ }
+ if (length < 0) {
+ length = LONG_MAX;
+ }
+ resetDataSource();
+ mType = TYPE_FD;
+ mFD = fd;
+ mFDOffset = offset;
+ mFDLength = length;
+ return this;
+ }
+
+ /**
+ * Sets the data source as a content Uri.
+ *
+ * @param context the Context to use when resolving the Uri
+ * @param uri the Content URI of the data you want to play
+ * @return the same Builder instance.
+ * @throws NullPointerException if context or uri is null.
+ */
+ public Builder setDataSource(@NonNull Context context, @NonNull Uri uri) {
+ Preconditions.checkNotNull(context, "context cannot be null");
+ Preconditions.checkNotNull(uri, "uri cannot be null");
+ resetDataSource();
+ mType = TYPE_URI;
+ mUri = uri;
+ mUriContext = context;
+ return this;
+ }
+
+ /**
+ * Sets the data source as a content Uri.
+ *
+ * To provide cookies for the subsequent HTTP requests, you can install your own default
+ * cookie handler and use other variants of setDataSource APIs instead. Alternatively, you
+ * can use this API to pass the cookies as a list of HttpCookie. If the app has not
+ * installed a CookieHandler already, {@link MediaPlayer2} will create a CookieManager
+ * and populates its CookieStore with the provided cookies when this data source is passed
+ * to {@link MediaPlayer2}. If the app has installed its own handler already, the handler
+ * is required to be of CookieManager type such that {@link MediaPlayer2} can update the
+ * manager’s CookieStore.
+ *
+ * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
+ * but that can be changed with key/value pairs through the headers parameter with
+ * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
+ * disallow or allow cross domain redirection.
+ *
+ * @param context the Context to use when resolving the Uri
+ * @param uri the Content URI of the data you want to play
+ * @param headers the headers to be sent together with the request for the data
+ * The headers must not include cookies. Instead, use the cookies param.
+ * @param cookies the cookies to be sent together with the request
+ * @return the same Builder instance.
+ * @throws NullPointerException if context or uri is null.
+ */
+ public Builder setDataSource(@NonNull Context context, @NonNull Uri uri,
+ @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies) {
+ Preconditions.checkNotNull(uri);
+ resetDataSource();
+ mType = TYPE_URI;
+ mUri = uri;
+ if (headers != null) {
+ mUriHeader = new HashMap<String, String>(headers);
+ }
+ if (cookies != null) {
+ mUriCookies = new ArrayList<HttpCookie>(cookies);
+ }
+ mUriContext = context;
+ return this;
+ }
+
+ private void resetDataSource() {
+ mType = TYPE_NONE;
+ mMedia2DataSource = null;
+ mFD = null;
+ mFDOffset = 0;
+ mFDLength = LONG_MAX;
+ mUri = null;
+ mUriHeader = null;
+ mUriCookies = null;
+ mUriContext = null;
+ }
+ }
+}
diff --git a/media/java/android/media/IMediaSession2.aidl b/media/java/android/media/IMediaSession2.aidl
index 078b611..b10a40b 100644
--- a/media/java/android/media/IMediaSession2.aidl
+++ b/media/java/android/media/IMediaSession2.aidl
@@ -16,7 +16,6 @@
package android.media;
-import android.media.session.PlaybackState;
import android.media.IMediaSession2Callback;
import android.os.Bundle;
@@ -44,7 +43,7 @@
//////////////////////////////////////////////////////////////////////////////////////////////
oneway void sendCommand(IMediaSession2Callback caller, in Bundle command, in Bundle args);
- PlaybackState getPlaybackState();
+ Bundle getPlaybackState();
//////////////////////////////////////////////////////////////////////////////////////////////
// Get library service specific
diff --git a/media/java/android/media/IMediaSession2Callback.aidl b/media/java/android/media/IMediaSession2Callback.aidl
index 1664e01..eb02fa7 100644
--- a/media/java/android/media/IMediaSession2Callback.aidl
+++ b/media/java/android/media/IMediaSession2Callback.aidl
@@ -29,7 +29,7 @@
* @hide
*/
oneway interface IMediaSession2Callback {
- void onPlaybackStateChanged(in PlaybackState state);
+ void onPlaybackStateChanged(in Bundle state);
/**
* Called only when the controller is created with service's token.
diff --git a/media/java/android/media/Media2DataSource.java b/media/java/android/media/Media2DataSource.java
new file mode 100644
index 0000000..8ee4a70
--- /dev/null
+++ b/media/java/android/media/Media2DataSource.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 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;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * For supplying media data to the framework. Implement this if your app has
+ * special requirements for the way media data is obtained.
+ *
+ * <p class="note">Methods of this interface may be called on multiple different
+ * threads. There will be a thread synchronization point between each call to ensure that
+ * modifications to the state of your Media2DataSource are visible to future calls. This means
+ * you don't need to do your own synchronization unless you're modifying the
+ * Media2DataSource from another thread while it's being used by the framework.</p>
+ *
+ */
+public abstract class Media2DataSource implements Closeable {
+ /**
+ * Called to request data from the given position.
+ *
+ * Implementations should should write up to {@code size} bytes into
+ * {@code buffer}, and return the number of bytes written.
+ *
+ * Return {@code 0} if size is zero (thus no bytes are read).
+ *
+ * Return {@code -1} to indicate that end of stream is reached.
+ *
+ * @param position the position in the data source to read from.
+ * @param buffer the buffer to read the data into.
+ * @param offset the offset within buffer to read the data into.
+ * @param size the number of bytes to read.
+ * @throws IOException on fatal errors.
+ * @return the number of bytes read, or -1 if there was an error.
+ */
+ public abstract int readAt(long position, byte[] buffer, int offset, int size)
+ throws IOException;
+
+ /**
+ * Called to get the size of the data source.
+ *
+ * @throws IOException on fatal errors
+ * @return the size of data source in bytes, or -1 if the size is unknown.
+ */
+ public abstract long getSize() throws IOException;
+}
diff --git a/media/java/android/media/Media2HTTPConnection.java b/media/java/android/media/Media2HTTPConnection.java
new file mode 100644
index 0000000..0d7825a
--- /dev/null
+++ b/media/java/android/media/Media2HTTPConnection.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright 2017 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;
+
+import android.net.NetworkUtils;
+import android.os.StrictMode;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.Proxy;
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.NoRouteToHostException;
+import java.net.ProtocolException;
+import java.net.UnknownServiceException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static android.media.MediaPlayer2.MEDIA_ERROR_UNSUPPORTED;
+
+/** @hide */
+public class Media2HTTPConnection {
+ private static final String TAG = "Media2HTTPConnection";
+ private static final boolean VERBOSE = false;
+
+ // connection timeout - 30 sec
+ private static final int CONNECT_TIMEOUT_MS = 30 * 1000;
+
+ private long mCurrentOffset = -1;
+ private URL mURL = null;
+ private Map<String, String> mHeaders = null;
+ private HttpURLConnection mConnection = null;
+ private long mTotalSize = -1;
+ private InputStream mInputStream = null;
+
+ private boolean mAllowCrossDomainRedirect = true;
+ private boolean mAllowCrossProtocolRedirect = true;
+
+ // from com.squareup.okhttp.internal.http
+ private final static int HTTP_TEMP_REDIRECT = 307;
+ private final static int MAX_REDIRECTS = 20;
+
+ public Media2HTTPConnection() {
+ CookieHandler cookieHandler = CookieHandler.getDefault();
+ if (cookieHandler == null) {
+ Log.w(TAG, "Media2HTTPConnection: Unexpected. No CookieHandler found.");
+ }
+ }
+
+ public boolean connect(String uri, String headers) {
+ if (VERBOSE) {
+ Log.d(TAG, "connect: uri=" + uri + ", headers=" + headers);
+ }
+
+ try {
+ disconnect();
+ mAllowCrossDomainRedirect = true;
+ mURL = new URL(uri);
+ mHeaders = convertHeaderStringToMap(headers);
+ } catch (MalformedURLException e) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean parseBoolean(String val) {
+ try {
+ return Long.parseLong(val) != 0;
+ } catch (NumberFormatException e) {
+ return "true".equalsIgnoreCase(val) ||
+ "yes".equalsIgnoreCase(val);
+ }
+ }
+
+ /* returns true iff header is internal */
+ private boolean filterOutInternalHeaders(String key, String val) {
+ if ("android-allow-cross-domain-redirect".equalsIgnoreCase(key)) {
+ mAllowCrossDomainRedirect = parseBoolean(val);
+ // cross-protocol redirects are also controlled by this flag
+ mAllowCrossProtocolRedirect = mAllowCrossDomainRedirect;
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ private Map<String, String> convertHeaderStringToMap(String headers) {
+ HashMap<String, String> map = new HashMap<String, String>();
+
+ String[] pairs = headers.split("\r\n");
+ for (String pair : pairs) {
+ int colonPos = pair.indexOf(":");
+ if (colonPos >= 0) {
+ String key = pair.substring(0, colonPos);
+ String val = pair.substring(colonPos + 1);
+
+ if (!filterOutInternalHeaders(key, val)) {
+ map.put(key, val);
+ }
+ }
+ }
+
+ return map;
+ }
+
+ public void disconnect() {
+ teardownConnection();
+ mHeaders = null;
+ mURL = null;
+ }
+
+ private void teardownConnection() {
+ if (mConnection != null) {
+ if (mInputStream != null) {
+ try {
+ mInputStream.close();
+ } catch (IOException e) {
+ }
+ mInputStream = null;
+ }
+
+ mConnection.disconnect();
+ mConnection = null;
+
+ mCurrentOffset = -1;
+ }
+ }
+
+ private static final boolean isLocalHost(URL url) {
+ if (url == null) {
+ return false;
+ }
+
+ String host = url.getHost();
+
+ if (host == null) {
+ return false;
+ }
+
+ try {
+ if (host.equalsIgnoreCase("localhost")) {
+ return true;
+ }
+ if (NetworkUtils.numericToInetAddress(host).isLoopbackAddress()) {
+ return true;
+ }
+ } catch (IllegalArgumentException iex) {
+ }
+ return false;
+ }
+
+ private void seekTo(long offset) throws IOException {
+ teardownConnection();
+
+ try {
+ int response;
+ int redirectCount = 0;
+
+ URL url = mURL;
+
+ // do not use any proxy for localhost (127.0.0.1)
+ boolean noProxy = isLocalHost(url);
+
+ while (true) {
+ if (noProxy) {
+ mConnection = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY);
+ } else {
+ mConnection = (HttpURLConnection)url.openConnection();
+ }
+ mConnection.setConnectTimeout(CONNECT_TIMEOUT_MS);
+
+ // handle redirects ourselves if we do not allow cross-domain redirect
+ mConnection.setInstanceFollowRedirects(mAllowCrossDomainRedirect);
+
+ if (mHeaders != null) {
+ for (Map.Entry<String, String> entry : mHeaders.entrySet()) {
+ mConnection.setRequestProperty(
+ entry.getKey(), entry.getValue());
+ }
+ }
+
+ if (offset > 0) {
+ mConnection.setRequestProperty(
+ "Range", "bytes=" + offset + "-");
+ }
+
+ response = mConnection.getResponseCode();
+ if (response != HttpURLConnection.HTTP_MULT_CHOICE &&
+ response != HttpURLConnection.HTTP_MOVED_PERM &&
+ response != HttpURLConnection.HTTP_MOVED_TEMP &&
+ response != HttpURLConnection.HTTP_SEE_OTHER &&
+ response != HTTP_TEMP_REDIRECT) {
+ // not a redirect, or redirect handled by HttpURLConnection
+ break;
+ }
+
+ if (++redirectCount > MAX_REDIRECTS) {
+ throw new NoRouteToHostException("Too many redirects: " + redirectCount);
+ }
+
+ String method = mConnection.getRequestMethod();
+ if (response == HTTP_TEMP_REDIRECT &&
+ !method.equals("GET") && !method.equals("HEAD")) {
+ // "If the 307 status code is received in response to a
+ // request other than GET or HEAD, the user agent MUST NOT
+ // automatically redirect the request"
+ throw new NoRouteToHostException("Invalid redirect");
+ }
+ String location = mConnection.getHeaderField("Location");
+ if (location == null) {
+ throw new NoRouteToHostException("Invalid redirect");
+ }
+ url = new URL(mURL /* TRICKY: don't use url! */, location);
+ if (!url.getProtocol().equals("https") &&
+ !url.getProtocol().equals("http")) {
+ throw new NoRouteToHostException("Unsupported protocol redirect");
+ }
+ boolean sameProtocol = mURL.getProtocol().equals(url.getProtocol());
+ if (!mAllowCrossProtocolRedirect && !sameProtocol) {
+ throw new NoRouteToHostException("Cross-protocol redirects are disallowed");
+ }
+ boolean sameHost = mURL.getHost().equals(url.getHost());
+ if (!mAllowCrossDomainRedirect && !sameHost) {
+ throw new NoRouteToHostException("Cross-domain redirects are disallowed");
+ }
+
+ if (response != HTTP_TEMP_REDIRECT) {
+ // update effective URL, unless it is a Temporary Redirect
+ mURL = url;
+ }
+ }
+
+ if (mAllowCrossDomainRedirect) {
+ // remember the current, potentially redirected URL if redirects
+ // were handled by HttpURLConnection
+ mURL = mConnection.getURL();
+ }
+
+ if (response == HttpURLConnection.HTTP_PARTIAL) {
+ // Partial content, we cannot just use getContentLength
+ // because what we want is not just the length of the range
+ // returned but the size of the full content if available.
+
+ String contentRange =
+ mConnection.getHeaderField("Content-Range");
+
+ mTotalSize = -1;
+ if (contentRange != null) {
+ // format is "bytes xxx-yyy/zzz
+ // where "zzz" is the total number of bytes of the
+ // content or '*' if unknown.
+
+ int lastSlashPos = contentRange.lastIndexOf('/');
+ if (lastSlashPos >= 0) {
+ String total =
+ contentRange.substring(lastSlashPos + 1);
+
+ try {
+ mTotalSize = Long.parseLong(total);
+ } catch (NumberFormatException e) {
+ }
+ }
+ }
+ } else if (response != HttpURLConnection.HTTP_OK) {
+ throw new IOException();
+ } else {
+ mTotalSize = mConnection.getContentLength();
+ }
+
+ if (offset > 0 && response != HttpURLConnection.HTTP_PARTIAL) {
+ // Some servers simply ignore "Range" requests and serve
+ // data from the start of the content.
+ throw new ProtocolException();
+ }
+
+ mInputStream =
+ new BufferedInputStream(mConnection.getInputStream());
+
+ mCurrentOffset = offset;
+ } catch (IOException e) {
+ mTotalSize = -1;
+ teardownConnection();
+ mCurrentOffset = -1;
+
+ throw e;
+ }
+ }
+
+ public int readAt(long offset, byte[] data, int size) {
+ StrictMode.ThreadPolicy policy =
+ new StrictMode.ThreadPolicy.Builder().permitAll().build();
+
+ StrictMode.setThreadPolicy(policy);
+
+ try {
+ if (offset != mCurrentOffset) {
+ seekTo(offset);
+ }
+
+ int n = mInputStream.read(data, 0, size);
+
+ if (n == -1) {
+ // InputStream signals EOS using a -1 result, our semantics
+ // are to return a 0-length read.
+ n = 0;
+ }
+
+ mCurrentOffset += n;
+
+ if (VERBOSE) {
+ Log.d(TAG, "readAt " + offset + " / " + size + " => " + n);
+ }
+
+ return n;
+ } catch (ProtocolException e) {
+ Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
+ return MEDIA_ERROR_UNSUPPORTED;
+ } catch (NoRouteToHostException e) {
+ Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
+ return MEDIA_ERROR_UNSUPPORTED;
+ } catch (UnknownServiceException e) {
+ Log.w(TAG, "readAt " + offset + " / " + size + " => " + e);
+ return MEDIA_ERROR_UNSUPPORTED;
+ } catch (IOException e) {
+ if (VERBOSE) {
+ Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
+ }
+ return -1;
+ } catch (Exception e) {
+ if (VERBOSE) {
+ Log.d(TAG, "unknown exception " + e);
+ Log.d(TAG, "readAt " + offset + " / " + size + " => -1");
+ }
+ return -1;
+ }
+ }
+
+ public long getSize() {
+ if (mConnection == null) {
+ try {
+ seekTo(0);
+ } catch (IOException e) {
+ return -1;
+ }
+ }
+
+ return mTotalSize;
+ }
+
+ public String getMIMEType() {
+ if (mConnection == null) {
+ try {
+ seekTo(0);
+ } catch (IOException e) {
+ return "application/octet-stream";
+ }
+ }
+
+ return mConnection.getContentType();
+ }
+
+ public String getUri() {
+ return mURL.toString();
+ }
+}
diff --git a/media/java/android/media/Media2HTTPService.java b/media/java/android/media/Media2HTTPService.java
new file mode 100644
index 0000000..957acec
--- /dev/null
+++ b/media/java/android/media/Media2HTTPService.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.util.Log;
+
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.util.List;
+
+/** @hide */
+public class Media2HTTPService {
+ private static final String TAG = "Media2HTTPService";
+ private List<HttpCookie> mCookies;
+ private Boolean mCookieStoreInitialized = new Boolean(false);
+
+ public Media2HTTPService(List<HttpCookie> cookies) {
+ mCookies = cookies;
+ Log.v(TAG, "Media2HTTPService(" + this + "): Cookies: " + cookies);
+ }
+
+ public Media2HTTPConnection makeHTTPConnection() {
+
+ synchronized (mCookieStoreInitialized) {
+ // Only need to do it once for all connections
+ if ( !mCookieStoreInitialized ) {
+ CookieHandler cookieHandler = CookieHandler.getDefault();
+ if (cookieHandler == null) {
+ cookieHandler = new CookieManager();
+ CookieHandler.setDefault(cookieHandler);
+ Log.v(TAG, "makeHTTPConnection: CookieManager created: " + cookieHandler);
+ } else {
+ Log.v(TAG, "makeHTTPConnection: CookieHandler (" + cookieHandler + ") exists.");
+ }
+
+ // Applying the bootstrapping cookies
+ if ( mCookies != null ) {
+ if ( cookieHandler instanceof CookieManager ) {
+ CookieManager cookieManager = (CookieManager)cookieHandler;
+ CookieStore store = cookieManager.getCookieStore();
+ for ( HttpCookie cookie : mCookies ) {
+ try {
+ store.add(null, cookie);
+ } catch ( Exception e ) {
+ Log.v(TAG, "makeHTTPConnection: CookieStore.add" + e);
+ }
+ //for extended debugging when needed
+ //Log.v(TAG, "MediaHTTPConnection adding Cookie[" + cookie.getName() +
+ // "]: " + cookie);
+ }
+ } else {
+ Log.w(TAG, "makeHTTPConnection: The installed CookieHandler is not a "
+ + "CookieManager. Can’t add the provided cookies to the cookie "
+ + "store.");
+ }
+ } // mCookies
+
+ mCookieStoreInitialized = true;
+
+ Log.v(TAG, "makeHTTPConnection(" + this + "): cookieHandler: " + cookieHandler +
+ " Cookies: " + mCookies);
+ } // mCookieStoreInitialized
+ } // synchronized
+
+ return new Media2HTTPConnection();
+ }
+
+ /* package private */ static Media2HTTPService createHTTPService(String path) {
+ return createHTTPService(path, null);
+ }
+
+ // when cookies are provided
+ static Media2HTTPService createHTTPService(String path, List<HttpCookie> cookies) {
+ if (path.startsWith("http://") || path.startsWith("https://")) {
+ return (new Media2HTTPService(cookies));
+ } else if (path.startsWith("widevine://")) {
+ Log.d(TAG, "Widevine classic is no longer supported");
+ }
+
+ return null;
+ }
+}
diff --git a/media/java/android/media/MediaBrowser2.java b/media/java/android/media/MediaBrowser2.java
index 33377bc..5ad4313 100644
--- a/media/java/android/media/MediaBrowser2.java
+++ b/media/java/android/media/MediaBrowser2.java
@@ -16,8 +16,10 @@
package android.media;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.content.Context;
import android.media.update.ApiLoader;
import android.media.update.MediaBrowser2Provider;
@@ -99,17 +101,17 @@
@Nullable Bundle options, @Nullable List<MediaItem2> result) { }
}
- public MediaBrowser2(Context context, SessionToken token, BrowserCallback callback,
- Executor executor) {
- super(context, token, callback, executor);
+ public MediaBrowser2(@NonNull Context context, @NonNull SessionToken2 token,
+ @NonNull @CallbackExecutor Executor executor, @NonNull BrowserCallback callback) {
+ super(context, token, executor, callback);
mProvider = (MediaBrowser2Provider) getProvider();
}
@Override
- MediaBrowser2Provider createProvider(Context context, SessionToken token,
- ControllerCallback callback, Executor executor) {
+ MediaBrowser2Provider createProvider(Context context, SessionToken2 token,
+ Executor executor, ControllerCallback callback) {
return ApiLoader.getProvider(context)
- .createMediaBrowser2(this, context, token, (BrowserCallback) callback, executor);
+ .createMediaBrowser2(context, this, token, executor, (BrowserCallback) callback);
}
public void getBrowserRoot(Bundle rootHints) {
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index f41e33f..44d9099 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -2639,7 +2639,8 @@
/**
* Returns the supported range of quality values.
*
- * @hide
+ * Quality is implementation-specific. As a general rule, a higher quality
+ * setting results in a better image quality and a lower compression ratio.
*/
public Range<Integer> getQualityRange() {
return mQualityRange;
@@ -2751,7 +2752,7 @@
}
if (info.containsKey("feature-bitrate-modes")) {
for (String mode: info.getString("feature-bitrate-modes").split(",")) {
- mBitControl |= parseBitrateMode(mode);
+ mBitControl |= (1 << parseBitrateMode(mode));
}
}
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index dca1027..6064ec4 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -16,15 +16,17 @@
package android.media;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.content.Context;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.PlaylistParam;
+import android.media.MediaSession2.PlaylistParams;
import android.media.session.MediaSessionManager;
import android.media.update.ApiLoader;
import android.media.update.MediaController2Provider;
@@ -54,7 +56,7 @@
* <p>
* A controller can be created through token from {@link MediaSessionManager} if you hold the
* signature|privileged permission "android.permission.MEDIA_CONTENT_CONTROL" permission or are
- * an enabled notification listener or by getting a {@link SessionToken} directly the
+ * an enabled notification listener or by getting a {@link SessionToken2} directly the
* the session owner.
* <p>
* MediaController2 objects are thread-safe.
@@ -63,8 +65,6 @@
* @see MediaSessionService2
* @hide
*/
-// TODO(jaewan): Unhide
-// TODO(jaewan): Revisit comments. Currently MediaBrowser case is missing.
public class MediaController2 implements AutoCloseable {
/**
* Interface for listening to change in activeness of the {@link MediaSession2}. It's
@@ -130,7 +130,7 @@
* @param param
*/
public void onPlaylistChanged(
- @NonNull List<MediaItem2> list, @NonNull PlaylistParam param) { }
+ @NonNull List<MediaItem2> list, @NonNull PlaylistParams param) { }
/**
* Called when the playback state is changed.
@@ -234,17 +234,17 @@
private final MediaController2Provider mProvider;
/**
- * Create a {@link MediaController2} from the {@link SessionToken}. This connects to the session
+ * Create a {@link MediaController2} from the {@link SessionToken2}. This connects to the session
* and may wake up the service if it's not available.
*
* @param context Context
* @param token token to connect to
- * @param callback controller callback to receive changes in
* @param executor executor to run callbacks on.
+ * @param callback controller callback to receive changes in
*/
// TODO(jaewan): Put @CallbackExecutor to the constructor.
- public MediaController2(@NonNull Context context, @NonNull SessionToken token,
- @NonNull ControllerCallback callback, @NonNull Executor executor) {
+ public MediaController2(@NonNull Context context, @NonNull SessionToken2 token,
+ @NonNull @CallbackExecutor Executor executor, @NonNull ControllerCallback callback) {
super();
// This also connects to the token.
@@ -252,14 +252,14 @@
// session whose session binder is only valid while it's active.
// prevent a controller from reusable after the
// session is released and recreated.
- mProvider = createProvider(context, token, callback, executor);
+ mProvider = createProvider(context, token, executor, callback);
}
MediaController2Provider createProvider(@NonNull Context context,
- @NonNull SessionToken token, @NonNull ControllerCallback callback,
- @NonNull Executor executor) {
+ @NonNull SessionToken2 token, @NonNull Executor executor,
+ @NonNull ControllerCallback callback) {
return ApiLoader.getProvider(context)
- .createMediaController2(this, context, token, callback, executor);
+ .createMediaController2(context, this, token, executor, callback);
}
/**
@@ -271,9 +271,7 @@
mProvider.close_impl();
}
- /**
- * @hide
- */
+ @SystemApi
public MediaController2Provider getProvider() {
return mProvider;
}
@@ -281,7 +279,8 @@
/**
* @return token
*/
- public @NonNull SessionToken getSessionToken() {
+ public @NonNull
+ SessionToken2 getSessionToken() {
return mProvider.getSessionToken_impl();
}
@@ -577,7 +576,8 @@
return mProvider.getPlaylist_impl();
}
- public @Nullable PlaylistParam getPlaylistParam() {
+ public @Nullable
+ PlaylistParams getPlaylistParam() {
return mProvider.getPlaylistParam_impl();
}
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 063186d..a0edefa 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -1253,8 +1253,6 @@
*
* Additional vendor-specific fields may also be present in
* the return value.
- *
- * @hide - not part of the public API at this time
*/
public PersistableBundle getMetrics() {
PersistableBundle bundle = getMetricsNative();
@@ -1571,8 +1569,6 @@
/**
* Definitions for the metrics that are reported via the
* {@link #getMetrics} call.
- *
- * @hide - not part of the public API at this time
*/
public final static class MetricsConstants
{
@@ -1582,16 +1578,350 @@
* Key to extract the number of successful {@link #openSession} calls
* from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String OPEN_SESSION_OK_COUNT
- = "/drm/mediadrm/open_session/ok/count";
+ = "drm.mediadrm.open_session.ok.count";
/**
* Key to extract the number of failed {@link #openSession} calls
* from the {@link PersistableBundle} returned by a
* {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
*/
public static final String OPEN_SESSION_ERROR_COUNT
- = "/drm/mediadrm/open_session/error/count";
+ = "drm.mediadrm.open_session.error.count";
+
+ /**
+ * Key to extract the list of error codes that were returned from
+ * {@link #openSession} calls. The key is used to lookup the list
+ * in the {@link PersistableBundle} returned by a {@link #getMetrics}
+ * call.
+ * The list is an array of Long values
+ * ({@link android.os.BaseBundle#getLongArray}).
+ */
+ public static final String OPEN_SESSION_ERROR_LIST
+ = "drm.mediadrm.open_session.error.list";
+
+ /**
+ * Key to extract the number of successful {@link #closeSession} calls
+ * from the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String CLOSE_SESSION_OK_COUNT
+ = "drm.mediadrm.close_session.ok.count";
+
+ /**
+ * Key to extract the number of failed {@link #closeSession} calls
+ * from the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String CLOSE_SESSION_ERROR_COUNT
+ = "drm.mediadrm.close_session.error.count";
+
+ /**
+ * Key to extract the list of error codes that were returned from
+ * {@link #closeSession} calls. The key is used to lookup the list
+ * in the {@link PersistableBundle} returned by a {@link #getMetrics}
+ * call.
+ * The list is an array of Long values
+ * ({@link android.os.BaseBundle#getLongArray}).
+ */
+ public static final String CLOSE_SESSION_ERROR_LIST
+ = "drm.mediadrm.close_session.error.list";
+
+ /**
+ * Key to extract the start times of sessions. Times are
+ * represented as milliseconds since epoch (1970-01-01T00:00:00Z).
+ * The start times are returned from the {@link PersistableBundle}
+ * from a {@link #getMetrics} call.
+ * The start times are returned as another {@link PersistableBundle}
+ * containing the session ids as keys and the start times as long
+ * values. Use {@link android.os.BaseBundle#keySet} to get the list of
+ * session ids, and then {@link android.os.BaseBundle#getLong} to get
+ * the start time for each session.
+ */
+ public static final String SESSION_START_TIMES_MS
+ = "drm.mediadrm.session_start_times_ms";
+
+ /**
+ * Key to extract the end times of sessions. Times are
+ * represented as milliseconds since epoch (1970-01-01T00:00:00Z).
+ * The end times are returned from the {@link PersistableBundle}
+ * from a {@link #getMetrics} call.
+ * The end times are returned as another {@link PersistableBundle}
+ * containing the session ids as keys and the end times as long
+ * values. Use {@link android.os.BaseBundle#keySet} to get the list of
+ * session ids, and then {@link android.os.BaseBundle#getLong} to get
+ * the end time for each session.
+ */
+ public static final String SESSION_END_TIMES_MS
+ = "drm.mediadrm.session_end_times_ms";
+
+ /**
+ * Key to extract the number of successful {@link #getKeyRequest} calls
+ * from the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String GET_KEY_REQUEST_OK_COUNT
+ = "drm.mediadrm.get_key_request.ok.count";
+
+ /**
+ * Key to extract the number of failed {@link #getKeyRequest}
+ * calls from the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String GET_KEY_REQUEST_ERROR_COUNT
+ = "drm.mediadrm.get_key_request.error.count";
+
+ /**
+ * Key to extract the list of error codes that were returned from
+ * {@link #getKeyRequest} calls. The key is used to lookup the list
+ * in the {@link PersistableBundle} returned by a {@link #getMetrics}
+ * call.
+ * The list is an array of Long values
+ * ({@link android.os.BaseBundle#getLongArray}).
+ */
+ public static final String GET_KEY_REQUEST_ERROR_LIST
+ = "drm.mediadrm.get_key_request.error.list";
+
+ /**
+ * Key to extract the average time in microseconds of calls to
+ * {@link #getKeyRequest}. The value is retrieved from the
+ * {@link PersistableBundle} returned from {@link #getMetrics}.
+ * The time is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String GET_KEY_REQUEST_OK_TIME_MICROS
+ = "drm.mediadrm.get_key_request.ok.average_time_micros";
+
+ /**
+ * Key to extract the number of successful {@link #provideKeyResponse}
+ * calls from the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String PROVIDE_KEY_RESPONSE_OK_COUNT
+ = "drm.mediadrm.provide_key_response.ok.count";
+
+ /**
+ * Key to extract the number of failed {@link #provideKeyResponse}
+ * calls from the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String PROVIDE_KEY_RESPONSE_ERROR_COUNT
+ = "drm.mediadrm.provide_key_response.error.count";
+
+ /**
+ * Key to extract the list of error codes that were returned from
+ * {@link #provideKeyResponse} calls. The key is used to lookup the
+ * list in the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The list is an array of Long values
+ * ({@link android.os.BaseBundle#getLongArray}).
+ */
+ public static final String PROVIDE_KEY_RESPONSE_ERROR_LIST
+ = "drm.mediadrm.provide_key_response.error.list";
+
+ /**
+ * Key to extract the average time in microseconds of calls to
+ * {@link #provideKeyResponse}. The valus is retrieved from the
+ * {@link PersistableBundle} returned from {@link #getMetrics}.
+ * The time is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String PROVIDE_KEY_RESPONSE_OK_TIME_MICROS
+ = "drm.mediadrm.provide_key_response.ok.average_time_micros";
+
+ /**
+ * Key to extract the number of successful {@link #getProvisionRequest}
+ * calls from the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String GET_PROVISION_REQUEST_OK_COUNT
+ = "drm.mediadrm.get_provision_request.ok.count";
+
+ /**
+ * Key to extract the number of failed {@link #getProvisionRequest}
+ * calls from the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String GET_PROVISION_REQUEST_ERROR_COUNT
+ = "drm.mediadrm.get_provision_request.error.count";
+
+ /**
+ * Key to extract the list of error codes that were returned from
+ * {@link #getProvisionRequest} calls. The key is used to lookup the
+ * list in the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The list is an array of Long values
+ * ({@link android.os.BaseBundle#getLongArray}).
+ */
+ public static final String GET_PROVISION_REQUEST_ERROR_LIST
+ = "drm.mediadrm.get_provision_request.error.list";
+
+ /**
+ * Key to extract the number of successful
+ * {@link #provideProvisionResponse} calls from the
+ * {@link PersistableBundle} returned by a {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String PROVIDE_PROVISION_RESPONSE_OK_COUNT
+ = "drm.mediadrm.provide_provision_response.ok.count";
+
+ /**
+ * Key to extract the number of failed
+ * {@link #provideProvisionResponse} calls from the
+ * {@link PersistableBundle} returned by a {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String PROVIDE_PROVISION_RESPONSE_ERROR_COUNT
+ = "drm.mediadrm.provide_provision_response.error.count";
+
+ /**
+ * Key to extract the list of error codes that were returned from
+ * {@link #provideProvisionResponse} calls. The key is used to lookup
+ * the list in the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The list is an array of Long values
+ * ({@link android.os.BaseBundle#getLongArray}).
+ */
+ public static final String PROVIDE_PROVISION_RESPONSE_ERROR_LIST
+ = "drm.mediadrm.provide_provision_response.error.list";
+
+ /**
+ * Key to extract the number of successful
+ * {@link #getPropertyByteArray} calls were made with the
+ * {@link #PROPERTY_DEVICE_UNIQUE_ID} value. The key is used to lookup
+ * the value in the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String GET_DEVICE_UNIQUE_ID_OK_COUNT
+ = "drm.mediadrm.get_device_unique_id.ok.count";
+
+ /**
+ * Key to extract the number of failed
+ * {@link #getPropertyByteArray} calls were made with the
+ * {@link #PROPERTY_DEVICE_UNIQUE_ID} value. The key is used to lookup
+ * the value in the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String GET_DEVICE_UNIQUE_ID_ERROR_COUNT
+ = "drm.mediadrm.get_device_unique_id.error.count";
+
+ /**
+ * Key to extract the list of error codes that were returned from
+ * {@link #getPropertyByteArray} calls with the
+ * {@link #PROPERTY_DEVICE_UNIQUE_ID} value. The key is used to lookup
+ * the list in the {@link PersistableBundle} returned by a
+ * {@link #getMetrics} call.
+ * The list is an array of Long values
+ * ({@link android.os.BaseBundle#getLongArray}).
+ */
+ public static final String GET_DEVICE_UNIQUE_ID_ERROR_LIST
+ = "drm.mediadrm.get_device_unique_id.error.list";
+
+ /**
+ * Key to extraact the count of {@link KeyStatus#STATUS_EXPIRED} events
+ * that occured. The count is extracted from the
+ * {@link PersistableBundle} returned from a {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String KEY_STATUS_EXPIRED_COUNT
+ = "drm.mediadrm.key_status.EXPIRED.count";
+
+ /**
+ * Key to extract the count of {@link KeyStatus#STATUS_INTERNAL_ERROR}
+ * events that occured. The count is extracted from the
+ * {@link PersistableBundle} returned from a {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String KEY_STATUS_INTERNAL_ERROR_COUNT
+ = "drm.mediadrm.key_status.INTERNAL_ERROR.count";
+
+ /**
+ * Key to extract the count of
+ * {@link KeyStatus#STATUS_OUTPUT_NOT_ALLOWED} events that occured.
+ * The count is extracted from the
+ * {@link PersistableBundle} returned from a {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String KEY_STATUS_OUTPUT_NOT_ALLOWED_COUNT
+ = "drm.mediadrm.key_status_change.OUTPUT_NOT_ALLOWED.count";
+
+ /**
+ * Key to extract the count of {@link KeyStatus#STATUS_PENDING}
+ * events that occured. The count is extracted from the
+ * {@link PersistableBundle} returned from a {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String KEY_STATUS_PENDING_COUNT
+ = "drm.mediadrm.key_status_change.PENDING.count";
+
+ /**
+ * Key to extract the count of {@link KeyStatus#STATUS_USABLE}
+ * events that occured. The count is extracted from the
+ * {@link PersistableBundle} returned from a {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String KEY_STATUS_USABLE_COUNT
+ = "drm.mediadrm.key_status_change.USABLE.count";
+
+ /**
+ * Key to extract the count of {@link OnEventListener#onEvent}
+ * calls of type PROVISION_REQUIRED occured. The count is
+ * extracted from the {@link PersistableBundle} returned from a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String EVENT_PROVISION_REQUIRED_COUNT
+ = "drm.mediadrm.event.PROVISION_REQUIRED.count";
+
+ /**
+ * Key to extract the count of {@link OnEventListener#onEvent}
+ * calls of type KEY_NEEDED occured. The count is
+ * extracted from the {@link PersistableBundle} returned from a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String EVENT_KEY_NEEDED_COUNT
+ = "drm.mediadrm.event.KEY_NEEDED.count";
+
+ /**
+ * Key to extract the count of {@link OnEventListener#onEvent}
+ * calls of type KEY_EXPIRED occured. The count is
+ * extracted from the {@link PersistableBundle} returned from a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String EVENT_KEY_EXPIRED_COUNT
+ = "drm.mediadrm.event.KEY_EXPIRED.count";
+
+ /**
+ * Key to extract the count of {@link OnEventListener#onEvent}
+ * calls of type VENDOR_DEFINED. The count is
+ * extracted from the {@link PersistableBundle} returned from a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String EVENT_VENDOR_DEFINED_COUNT
+ = "drm.mediadrm.event.VENDOR_DEFINED.count";
+
+ /**
+ * Key to extract the count of {@link OnEventListener#onEvent}
+ * calls of type SESSION_RECLAIMED. The count is
+ * extracted from the {@link PersistableBundle} returned from a
+ * {@link #getMetrics} call.
+ * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+ */
+ public static final String EVENT_SESSION_RECLAIMED_COUNT
+ = "drm.mediadrm.event.SESSION_RECLAIMED.count";
}
}
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 2c1b4b3..174d6a3 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -626,6 +626,12 @@
*/
public native long getSampleTime();
+ /**
+ * @return size of the current sample in bytes or -1 if no more
+ * samples are available.
+ */
+ public native long getSampleSize();
+
// Keep these in sync with their equivalents in NuMediaExtractor.h
/**
* The sample is a sync sample (or in {@link MediaCodec}'s terminology
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index c6496eb..e9128e4 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -601,8 +601,6 @@
* codec specific, but lower values generally result in more efficient
* (smaller-sized) encoding.
*
- * @hide
- *
* @see MediaCodecInfo.EncoderCapabilities#getQualityRange()
*/
public static final String KEY_QUALITY = "quality";
diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java
index 96a87d5..6a96faa 100644
--- a/media/java/android/media/MediaItem2.java
+++ b/media/java/android/media/MediaItem2.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Bundle;
import android.text.TextUtils;
@@ -32,15 +33,10 @@
* When it's sent to a controller or browser, it's anonymized and data descriptor wouldn't be sent.
* <p>
* This object isn't a thread safe.
- *
* @hide
*/
-// TODO(jaewan): Unhide and extends from DataSourceDesc.
-// Note) Feels like an anti-pattern. We should anonymize MediaItem2 to remove *all*
-// information in the DataSourceDesc. Why it should extends from this?
-// TODO(jaewan): Move this to updatable
-// Previously MediaBrowser.MediaItem
public class MediaItem2 {
+ // TODO(jaewan): Keep DataSourceDesc.
private final int mFlags;
private MediaMetadata2 mMetadata;
diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java
index b98936e..5dadcb5 100644
--- a/media/java/android/media/MediaLibraryService2.java
+++ b/media/java/android/media/MediaLibraryService2.java
@@ -16,6 +16,7 @@
package android.media;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
@@ -27,9 +28,9 @@
import android.media.update.MediaSession2Provider;
import android.media.update.MediaSessionService2Provider;
import android.os.Bundle;
-import android.service.media.MediaBrowserService.BrowserRoot;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* Base class for media library services.
@@ -52,7 +53,6 @@
* declare metadata in the manifest as follows.
* @hide
*/
-// TODO(jaewan): Unhide
public abstract class MediaLibraryService2 extends MediaSessionService2 {
/**
* This is the interface name that a service implementing a session service should say that it
@@ -67,20 +67,21 @@
private final MediaLibrarySessionProvider mProvider;
MediaLibrarySession(Context context, MediaPlayerBase player, String id,
- SessionCallback callback, VolumeProvider volumeProvider,
- int ratingType, PendingIntent sessionActivity) {
- super(context, player, id, callback, volumeProvider, ratingType, sessionActivity);
+ VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+ Executor callbackExecutor, SessionCallback callback) {
+ super(context, player, id, volumeProvider, ratingType, sessionActivity,
+ callbackExecutor, callback);
mProvider = (MediaLibrarySessionProvider) getProvider();
}
@Override
MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
- SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
- PendingIntent sessionActivity) {
+ VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+ Executor callbackExecutor, SessionCallback callback) {
return ApiLoader.getProvider(context)
- .createMediaLibraryService2MediaLibrarySession(this, context, player, id,
- (MediaLibrarySessionCallback) callback, volumeProvider, ratingType,
- sessionActivity);
+ .createMediaLibraryService2MediaLibrarySession(context, this, player, id,
+ volumeProvider, ratingType, sessionActivity,
+ callbackExecutor, (MediaLibrarySessionCallback) callback);
}
/**
@@ -206,24 +207,26 @@
extends BuilderBase<MediaLibrarySessionBuilder, MediaLibrarySessionCallback> {
public MediaLibrarySessionBuilder(
@NonNull Context context, @NonNull MediaPlayerBase player,
+ @NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull MediaLibrarySessionCallback callback) {
super(context, player);
- setSessionCallback(callback);
+ setSessionCallback(callbackExecutor, callback);
}
@Override
public MediaLibrarySessionBuilder setSessionCallback(
+ @NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull MediaLibrarySessionCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("MediaLibrarySessionCallback cannot be null");
}
- return super.setSessionCallback(callback);
+ return super.setSessionCallback(callbackExecutor, callback);
}
@Override
- public MediaLibrarySession build() throws IllegalStateException {
- return new MediaLibrarySession(mContext, mPlayer, mId, mCallback,
- mVolumeProvider, mRatingType, mSessionActivity);
+ public MediaLibrarySession build() {
+ return new MediaLibrarySession(mContext, mPlayer, mId, mVolumeProvider, mRatingType,
+ mSessionActivity, mCallbackExecutor, mCallback);
}
}
@@ -272,7 +275,7 @@
* @see #EXTRA_OFFLINE
* @see #EXTRA_SUGGESTED
*/
- public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+ public static final String EXTRA_RECENT = "android.media.extra.RECENT";
/**
* The lookup key for a boolean that indicates whether the browser service should return a
@@ -290,7 +293,7 @@
* @see #EXTRA_RECENT
* @see #EXTRA_SUGGESTED
*/
- public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+ public static final String EXTRA_OFFLINE = "android.media.extra.OFFLINE";
/**
* The lookup key for a boolean that indicates whether the browser service should return a
@@ -298,8 +301,8 @@
*
* <p>When creating a media browser for a given media browser service, this key can be
* supplied as a root hint for retrieving the media items suggested by the media browser
- * service. The list of media items passed in {@link android.media.browse.MediaBrowser.SubscriptionCallback#onChildrenLoaded(String, List)}
- * is considered ordered by relevance, first being the top suggestion.
+ * service. The list of media items is considered ordered by relevance, first being the top
+ * suggestion.
* If the media browser service can provide such media items, the implementation must return
* the key in the root hint when
* {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
@@ -309,7 +312,7 @@
* @see #EXTRA_RECENT
* @see #EXTRA_OFFLINE
*/
- public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+ public static final String EXTRA_SUGGESTED = "android.media.extra.SUGGESTED";
final private String mRootId;
final private Bundle mExtras;
diff --git a/media/java/android/media/MediaMetadata2.java b/media/java/android/media/MediaMetadata2.java
index 0e24db6..fcdb4f7 100644
--- a/media/java/android/media/MediaMetadata2.java
+++ b/media/java/android/media/MediaMetadata2.java
@@ -34,6 +34,7 @@
/**
* Contains metadata about an item, such as the title, artist, etc.
+ *
* @hide
*/
// TODO(jaewan): Move this to updatable
@@ -218,7 +219,7 @@
/**
* A Uri formatted String representing the content. This value is specific to the
* service providing the content. It may be used with
- * {@link MediaController2#playFromUri(Uri, Bundle)}
+ * {@link MediaController2#playFromUri(String, Bundle)}
* to initiate playback when provided by a {@link MediaBrowser2} connected to
* the same app.
*/
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
new file mode 100644
index 0000000..d36df84
--- /dev/null
+++ b/media/java/android/media/MediaPlayer2.java
@@ -0,0 +1,2476 @@
+/*
+ * Copyright 2017 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;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.media.MediaDrm;
+import android.media.MediaFormat;
+import android.media.MediaPlayer2Impl;
+import android.media.MediaTimeProvider;
+import android.media.PlaybackParams;
+import android.media.SubtitleController;
+import android.media.SubtitleController.Anchor;
+import android.media.SubtitleData;
+import android.media.SubtitleTrack.RenderingWidget;
+import android.media.SyncParams;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.AutoCloseable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+
+/**
+ * MediaPlayer2 class can be used to control playback
+ * of audio/video files and streams. An example on how to use the methods in
+ * this class can be found in {@link android.widget.VideoView}.
+ *
+ * <p>Topics covered here are:
+ * <ol>
+ * <li><a href="#StateDiagram">State Diagram</a>
+ * <li><a href="#Valid_and_Invalid_States">Valid and Invalid States</a>
+ * <li><a href="#Permissions">Permissions</a>
+ * <li><a href="#Callbacks">Register informational and error callbacks</a>
+ * </ol>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about how to use MediaPlayer2, read the
+ * <a href="{@docRoot}guide/topics/media/mediaplayer.html">Media Playback</a> developer guide.</p>
+ * </div>
+ *
+ * <a name="StateDiagram"></a>
+ * <h3>State Diagram</h3>
+ *
+ * <p>Playback control of audio/video files and streams is managed as a state
+ * machine. The following diagram shows the life cycle and the states of a
+ * MediaPlayer2 object driven by the supported playback control operations.
+ * The ovals represent the states a MediaPlayer2 object may reside
+ * in. The arcs represent the playback control operations that drive the object
+ * state transition. There are two types of arcs. The arcs with a single arrow
+ * head represent synchronous method calls, while those with
+ * a double arrow head represent asynchronous method calls.</p>
+ *
+ * <p><img src="../../../images/mediaplayer_state_diagram.gif"
+ * alt="MediaPlayer State diagram"
+ * border="0" /></p>
+ *
+ * <p>From this state diagram, one can see that a MediaPlayer2 object has the
+ * following states:</p>
+ * <ul>
+ * <li>When a MediaPlayer2 object is just created using <code>new</code> or
+ * after {@link #reset()} is called, it is in the <em>Idle</em> state; and after
+ * {@link #close()} is called, it is in the <em>End</em> state. Between these
+ * two states is the life cycle of the MediaPlayer2 object.
+ * <ul>
+ * <li>There is a subtle but important difference between a newly constructed
+ * MediaPlayer2 object and the MediaPlayer2 object after {@link #reset()}
+ * is called. It is a programming error to invoke methods such
+ * as {@link #getCurrentPosition()},
+ * {@link #getDuration()}, {@link #getVideoHeight()},
+ * {@link #getVideoWidth()}, {@link #setAudioAttributes(AudioAttributes)},
+ * {@link #setVolume(float, float)}, {@link #pause()}, {@link #play()},
+ * {@link #seekTo(long, int)} or
+ * {@link #prepareAsync()} in the <em>Idle</em> state for both cases. If any of these
+ * methods is called right after a MediaPlayer2 object is constructed,
+ * the user supplied callback method OnErrorListener.onError() won't be
+ * called by the internal player engine and the object state remains
+ * unchanged; but if these methods are called right after {@link #reset()},
+ * the user supplied callback method OnErrorListener.onError() will be
+ * invoked by the internal player engine and the object will be
+ * transfered to the <em>Error</em> state. </li>
+ * <li>It is also recommended that once
+ * a MediaPlayer2 object is no longer being used, call {@link #close()} immediately
+ * so that resources used by the internal player engine associated with the
+ * MediaPlayer2 object can be released immediately. Resource may include
+ * singleton resources such as hardware acceleration components and
+ * failure to call {@link #close()} may cause subsequent instances of
+ * MediaPlayer2 objects to fallback to software implementations or fail
+ * altogether. Once the MediaPlayer2
+ * object is in the <em>End</em> state, it can no longer be used and
+ * there is no way to bring it back to any other state. </li>
+ * <li>Furthermore,
+ * the MediaPlayer2 objects created using <code>new</code> is in the
+ * <em>Idle</em> state.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>In general, some playback control operation may fail due to various
+ * reasons, such as unsupported audio/video format, poorly interleaved
+ * audio/video, resolution too high, streaming timeout, and the like.
+ * Thus, error reporting and recovery is an important concern under
+ * these circumstances. Sometimes, due to programming errors, invoking a playback
+ * control operation in an invalid state may also occur. Under all these
+ * error conditions, the internal player engine invokes a user supplied
+ * EventCallback.onError() method if an EventCallback has been
+ * registered beforehand via
+ * {@link #registerEventCallback(Executor, EventCallback)}.
+ * <ul>
+ * <li>It is important to note that once an error occurs, the
+ * MediaPlayer2 object enters the <em>Error</em> state (except as noted
+ * above), even if an error listener has not been registered by the application.</li>
+ * <li>In order to reuse a MediaPlayer2 object that is in the <em>
+ * Error</em> state and recover from the error,
+ * {@link #reset()} can be called to restore the object to its <em>Idle</em>
+ * state.</li>
+ * <li>It is good programming practice to have your application
+ * register a OnErrorListener to look out for error notifications from
+ * the internal player engine.</li>
+ * <li>IllegalStateException is
+ * thrown to prevent programming errors such as calling
+ * {@link #prepareAsync()}, {@link #setDataSource(DataSourceDesc)}, or
+ * {@code setPlaylist} methods in an invalid state. </li>
+ * </ul>
+ * </li>
+ * <li>Calling
+ * {@link #setDataSource(DataSourceDesc)}, or
+ * {@code setPlaylist} transfers a
+ * MediaPlayer2 object in the <em>Idle</em> state to the
+ * <em>Initialized</em> state.
+ * <ul>
+ * <li>An IllegalStateException is thrown if
+ * setDataSource() or setPlaylist() is called in any other state.</li>
+ * <li>It is good programming
+ * practice to always look out for <code>IllegalArgumentException</code>
+ * and <code>IOException</code> that may be thrown from
+ * <code>setDataSource</code> and <code>setPlaylist</code> methods.</li>
+ * </ul>
+ * </li>
+ * <li>A MediaPlayer2 object must first enter the <em>Prepared</em> state
+ * before playback can be started.
+ * <ul>
+ * <li>There are an asynchronous way that the <em>Prepared</em> state can be reached:
+ * a call to {@link #prepareAsync()} (asynchronous) which
+ * first transfers the object to the <em>Preparing</em> state after the
+ * call returns (which occurs almost right way) while the internal
+ * player engine continues working on the rest of preparation work
+ * until the preparation work completes. When the preparation completes,
+ * the internal player engine then calls a user supplied callback method,
+ * onInfo() of the EventCallback interface with {@link #MEDIA_INFO_PREPARED}, if an
+ * EventCallback is registered beforehand via
+ * {@link #registerEventCallback(Executor, EventCallback)}.</li>
+ * <li>It is important to note that
+ * the <em>Preparing</em> state is a transient state, and the behavior
+ * of calling any method with side effect while a MediaPlayer2 object is
+ * in the <em>Preparing</em> state is undefined.</li>
+ * <li>An IllegalStateException is
+ * thrown if {@link #prepareAsync()} is called in
+ * any other state.</li>
+ * <li>While in the <em>Prepared</em> state, properties
+ * such as audio/sound volume, screenOnWhilePlaying, looping can be
+ * adjusted by invoking the corresponding set methods.</li>
+ * </ul>
+ * </li>
+ * <li>To start the playback, {@link #play()} must be called. After
+ * {@link #play()} returns successfully, the MediaPlayer2 object is in the
+ * <em>Started</em> state. {@link #isPlaying()} can be called to test
+ * whether the MediaPlayer2 object is in the <em>Started</em> state.
+ * <ul>
+ * <li>While in the <em>Started</em> state, the internal player engine calls
+ * a user supplied EventCallback.onBufferingUpdate() callback
+ * method if an EventCallback has been registered beforehand
+ * via {@link #registerEventCallback(Executor, EventCallback)}.
+ * This callback allows applications to keep track of the buffering status
+ * while streaming audio/video.</li>
+ * <li>Calling {@link #play()} has not effect
+ * on a MediaPlayer2 object that is already in the <em>Started</em> state.</li>
+ * </ul>
+ * </li>
+ * <li>Playback can be paused and stopped, and the current playback position
+ * can be adjusted. Playback can be paused via {@link #pause()}. When the call to
+ * {@link #pause()} returns, the MediaPlayer2 object enters the
+ * <em>Paused</em> state. Note that the transition from the <em>Started</em>
+ * state to the <em>Paused</em> state and vice versa happens
+ * asynchronously in the player engine. It may take some time before
+ * the state is updated in calls to {@link #isPlaying()}, and it can be
+ * a number of seconds in the case of streamed content.
+ * <ul>
+ * <li>Calling {@link #play()} to resume playback for a paused
+ * MediaPlayer2 object, and the resumed playback
+ * position is the same as where it was paused. When the call to
+ * {@link #play()} returns, the paused MediaPlayer2 object goes back to
+ * the <em>Started</em> state.</li>
+ * <li>Calling {@link #pause()} has no effect on
+ * a MediaPlayer2 object that is already in the <em>Paused</em> state.</li>
+ * </ul>
+ * </li>
+ * <li>The playback position can be adjusted with a call to
+ * {@link #seekTo(long, int)}.
+ * <ul>
+ * <li>Although the asynchronuous {@link #seekTo(long, int)}
+ * call returns right away, the actual seek operation may take a while to
+ * finish, especially for audio/video being streamed. When the actual
+ * seek operation completes, the internal player engine calls a user
+ * supplied EventCallback.onInfo() with {@link #MEDIA_INFO_COMPLETE_CALL_SEEK}
+ * if an EventCallback has been registered beforehand via
+ * {@link #registerEventCallback(Executor, EventCallback)}.</li>
+ * <li>Please
+ * note that {@link #seekTo(long, int)} can also be called in the other states,
+ * such as <em>Prepared</em>, <em>Paused</em> and <em>PlaybackCompleted
+ * </em> state. When {@link #seekTo(long, int)} is called in those states,
+ * one video frame will be displayed if the stream has video and the requested
+ * position is valid.
+ * </li>
+ * <li>Furthermore, the actual current playback position
+ * can be retrieved with a call to {@link #getCurrentPosition()}, which
+ * is helpful for applications such as a Music player that need to keep
+ * track of the playback progress.</li>
+ * </ul>
+ * </li>
+ * <li>When the playback reaches the end of stream, the playback completes.
+ * <ul>
+ * <li>If the looping mode was being set to one of the values of
+ * {@link #LOOPING_MODE_FULL}, {@link #LOOPING_MODE_SINGLE} or
+ * {@link #LOOPING_MODE_SHUFFLE} with
+ * {@link #setLoopingMode(int)}, the MediaPlayer2 object shall remain in
+ * the <em>Started</em> state.</li>
+ * <li>If the looping mode was set to <var>false
+ * </var>, the player engine calls a user supplied callback method,
+ * EventCallback.onCompletion(), if an EventCallback is registered
+ * beforehand via {@link #registerEventCallback(Executor, EventCallback)}.
+ * The invoke of the callback signals that the object is now in the <em>
+ * PlaybackCompleted</em> state.</li>
+ * <li>While in the <em>PlaybackCompleted</em>
+ * state, calling {@link #play()} can restart the playback from the
+ * beginning of the audio/video source.</li>
+ * </ul>
+ *
+ *
+ * <a name="Valid_and_Invalid_States"></a>
+ * <h3>Valid and invalid states</h3>
+ *
+ * <table border="0" cellspacing="0" cellpadding="0">
+ * <tr><td>Method Name </p></td>
+ * <td>Valid Sates </p></td>
+ * <td>Invalid States </p></td>
+ * <td>Comments </p></td></tr>
+ * <tr><td>attachAuxEffect </p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td>
+ * <td>{Idle, Error} </p></td>
+ * <td>This method must be called after setDataSource or setPlaylist.
+ * Calling it does not change the object state. </p></td></tr>
+ * <tr><td>getAudioSessionId </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>getCurrentPosition </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted} </p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change the
+ * state. Calling this method in an invalid state transfers the object
+ * to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>getDuration </p></td>
+ * <td>{Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td>
+ * <td>{Idle, Initialized, Error} </p></td>
+ * <td>Successful invoke of this method in a valid state does not change the
+ * state. Calling this method in an invalid state transfers the object
+ * to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>getVideoHeight </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change the
+ * state. Calling this method in an invalid state transfers the object
+ * to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>getVideoWidth </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an invalid state transfers the
+ * object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>isPlaying </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an invalid state transfers the
+ * object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>pause </p></td>
+ * <td>{Started, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Prepared, Stopped, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Paused</em> state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>prepareAsync </p></td>
+ * <td>{Initialized, Stopped} </p></td>
+ * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Preparing</em> state. Calling this method in an
+ * invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>release </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>After {@link #close()}, the object is no longer available. </p></td></tr>
+ * <tr><td>reset </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted, Error}</p></td>
+ * <td>{}</p></td>
+ * <td>After {@link #reset()}, the object is like being just created.</p></td></tr>
+ * <tr><td>seekTo </p></td>
+ * <td>{Prepared, Started, Paused, PlaybackCompleted} </p></td>
+ * <td>{Idle, Initialized, Stopped, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an invalid state transfers the
+ * object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>setAudioAttributes </p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method does not change the state. In order for the
+ * target audio attributes type to become effective, this method must be called before
+ * prepareAsync().</p></td></tr>
+ * <tr><td>setAudioSessionId </p></td>
+ * <td>{Idle} </p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
+ * Error} </p></td>
+ * <td>This method must be called in idle state as the audio session ID must be known before
+ * calling setDataSource or setPlaylist. Calling it does not change the object
+ * state. </p></td></tr>
+ * <tr><td>setAudioStreamType (deprecated)</p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method does not change the state. In order for the
+ * target audio stream type to become effective, this method must be called before
+ * prepareAsync().</p></td></tr>
+ * <tr><td>setAuxEffectSendLevel </p></td>
+ * <td>any</p></td>
+ * <td>{} </p></td>
+ * <td>Calling this method does not change the object state. </p></td></tr>
+ * <tr><td>setDataSource </p></td>
+ * <td>{Idle} </p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
+ * Error} </p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Initialized</em> state. Calling this method in an
+ * invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>setPlaylist </p></td>
+ * <td>{Idle} </p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
+ * Error} </p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Initialized</em> state. Calling this method in an
+ * invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>setDisplay </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setSurface </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setLoopingMode </p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>isLooping </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>registerDrmEventCallback </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>registerEventCallback </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setPlaybackParams</p></td>
+ * <td>{Initialized, Prepared, Started, Paused, PlaybackCompleted, Error}</p></td>
+ * <td>{Idle, Stopped} </p></td>
+ * <td>This method will change state in some cases, depending on when it's called.
+ * </p></td></tr>
+ * <tr><td>setVolume </p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.
+ * <tr><td>play </p></td>
+ * <td>{Prepared, Started, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Stopped, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Started</em> state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>stop </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Stopped</em> state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>getTrackInfo </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ * <tr><td>selectTrack </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ * <tr><td>deselectTrack </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ *
+ * </table>
+ *
+ * <a name="Permissions"></a>
+ * <h3>Permissions</h3>
+ * <p>One may need to declare a corresponding WAKE_LOCK permission {@link
+ * android.R.styleable#AndroidManifestUsesPermission <uses-permission>}
+ * element.
+ *
+ * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
+ * when used with network-based content.
+ *
+ * <a name="Callbacks"></a>
+ * <h3>Callbacks</h3>
+ * <p>Applications may want to register for informational and error
+ * events in order to be informed of some internal state update and
+ * possible runtime errors during playback or streaming. Registration for
+ * these events is done by properly setting the appropriate listeners (via calls
+ * to
+ * {@link #registerEventCallback(Executor, EventCallback)},
+ * {@link #registerDrmEventCallback(Executor, DrmEventCallback)}).
+ * In order to receive the respective callback
+ * associated with these listeners, applications are required to create
+ * MediaPlayer2 objects on a thread with its own Looper running (main UI
+ * thread by default has a Looper running).
+ *
+ */
+public abstract class MediaPlayer2 implements SubtitleController.Listener
+ , AudioRouting
+ , AutoCloseable
+{
+ /**
+ Constant to retrieve only the new metadata since the last
+ call.
+ // FIXME: unhide.
+ // FIXME: add link to getMetadata(boolean, boolean)
+ {@hide}
+ */
+ public static final boolean METADATA_UPDATE_ONLY = true;
+
+ /**
+ Constant to retrieve all the metadata.
+ // FIXME: unhide.
+ // FIXME: add link to getMetadata(boolean, boolean)
+ {@hide}
+ */
+ public static final boolean METADATA_ALL = false;
+
+ /**
+ Constant to enable the metadata filter during retrieval.
+ // FIXME: unhide.
+ // FIXME: add link to getMetadata(boolean, boolean)
+ {@hide}
+ */
+ public static final boolean APPLY_METADATA_FILTER = true;
+
+ /**
+ Constant to disable the metadata filter during retrieval.
+ // FIXME: unhide.
+ // FIXME: add link to getMetadata(boolean, boolean)
+ {@hide}
+ */
+ public static final boolean BYPASS_METADATA_FILTER = false;
+
+ /**
+ * Create a MediaPlayer2 object.
+ *
+ * @return A MediaPlayer2 object created
+ */
+ public static final MediaPlayer2 create() {
+ // TODO: load MediaUpdate APK
+ return new MediaPlayer2Impl();
+ }
+
+ /**
+ * @hide
+ */
+ // add hidden empty constructor so it doesn't show in SDK
+ public MediaPlayer2() { }
+
+ /**
+ * Create a request parcel which can be routed to the native media
+ * player using {@link #invoke(Parcel, Parcel)}. The Parcel
+ * returned has the proper InterfaceToken set. The caller should
+ * not overwrite that token, i.e it can only append data to the
+ * Parcel.
+ *
+ * @return A parcel suitable to hold a request for the native
+ * player.
+ * {@hide}
+ */
+ public Parcel newRequest() {
+ return null;
+ }
+
+ /**
+ * Invoke a generic method on the native player using opaque
+ * parcels for the request and reply. Both payloads' format is a
+ * convention between the java caller and the native player.
+ * Must be called after setDataSource or setPlaylist to make sure a native player
+ * exists. On failure, a RuntimeException is thrown.
+ *
+ * @param request Parcel with the data for the extension. The
+ * caller must use {@link #newRequest()} to get one.
+ *
+ * @param reply Output parcel with the data returned by the
+ * native player.
+ * {@hide}
+ */
+ public void invoke(Parcel request, Parcel reply) { }
+
+ /**
+ * Sets the {@link SurfaceHolder} to use for displaying the video
+ * portion of the media.
+ *
+ * Either a surface holder or surface must be set if a display or video sink
+ * is needed. Not calling this method or {@link #setSurface(Surface)}
+ * when playing back a video will result in only the audio track being played.
+ * A null surface holder or surface will result in only the audio track being
+ * played.
+ *
+ * @param sh the SurfaceHolder to use for video display
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released.
+ * @hide
+ */
+ public abstract void setDisplay(SurfaceHolder sh);
+
+ /**
+ * Sets the {@link Surface} to be used as the sink for the video portion of
+ * the media. Setting a
+ * Surface will un-set any Surface or SurfaceHolder that was previously set.
+ * A null surface will result in only the audio track being played.
+ *
+ * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps
+ * returned from {@link SurfaceTexture#getTimestamp()} will have an
+ * unspecified zero point. These timestamps cannot be directly compared
+ * between different media sources, different instances of the same media
+ * source, or multiple runs of the same program. The timestamp is normally
+ * monotonically increasing and is unaffected by time-of-day adjustments,
+ * but it is reset when the position is set.
+ *
+ * @param surface The {@link Surface} to be used for the video portion of
+ * the media.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released.
+ */
+ public abstract void setSurface(Surface surface);
+
+ /* Do not change these video scaling mode values below without updating
+ * their counterparts in system/window.h! Please do not forget to update
+ * {@link #isVideoScalingModeSupported} when new video scaling modes
+ * are added.
+ */
+ /**
+ * Specifies a video scaling mode. The content is stretched to the
+ * surface rendering area. When the surface has the same aspect ratio
+ * as the content, the aspect ratio of the content is maintained;
+ * otherwise, the aspect ratio of the content is not maintained when video
+ * is being rendered.
+ * There is no content cropping with this video scaling mode.
+ */
+ public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1;
+
+ /**
+ * Specifies a video scaling mode. The content is scaled, maintaining
+ * its aspect ratio. The whole surface area is always used. When the
+ * aspect ratio of the content is the same as the surface, no content
+ * is cropped; otherwise, content is cropped to fit the surface.
+ * @hide
+ */
+ public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2;
+
+ /**
+ * Sets video scaling mode. To make the target video scaling mode
+ * effective during playback, this method must be called after
+ * data source is set. If not called, the default video
+ * scaling mode is {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT}.
+ *
+ * <p> The supported video scaling modes are:
+ * <ul>
+ * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT}
+ * </ul>
+ *
+ * @param mode target video scaling mode. Must be one of the supported
+ * video scaling modes; otherwise, IllegalArgumentException will be thrown.
+ *
+ * @see MediaPlayer2#VIDEO_SCALING_MODE_SCALE_TO_FIT
+ * @hide
+ */
+ public void setVideoScalingMode(int mode) { }
+
+ /**
+ * Discards all pending commands.
+ */
+ public abstract void clearPendingCommands();
+
+ /**
+ * Sets the data source as described by a DataSourceDesc.
+ *
+ * @param dsd the descriptor of data source you want to play
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws NullPointerException if dsd is null
+ */
+ public abstract void setDataSource(@NonNull DataSourceDesc dsd) throws IOException;
+
+ /**
+ * Gets the current data source as described by a DataSourceDesc.
+ *
+ * @return the current DataSourceDesc
+ */
+ public abstract DataSourceDesc getCurrentDataSource();
+
+ /**
+ * Sets the play list.
+ *
+ * If startIndex falls outside play list range, it will be clamped to the nearest index
+ * in the play list.
+ *
+ * @param pl the play list of data source you want to play
+ * @param startIndex the index of the DataSourceDesc in the play list you want to play first
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws IllegalArgumentException if pl is null or empty, or pl contains null DataSourceDesc
+ */
+ public abstract void setPlaylist(@NonNull List<DataSourceDesc> pl, int startIndex)
+ throws IOException;
+
+ /**
+ * Gets a copy of the play list.
+ *
+ * @return a copy of the play list used by {@link MediaPlayer2}
+ */
+ public abstract List<DataSourceDesc> getPlaylist();
+
+ /**
+ * Sets the index of current DataSourceDesc in the play list to be played.
+ *
+ * @param index the index of DataSourceDesc in the play list you want to play
+ * @throws IllegalArgumentException if the play list is null
+ * @throws NullPointerException if index is outside play list range
+ */
+ public abstract void setCurrentPlaylistItem(int index);
+
+ /**
+ * Sets the index of next-to-be-played DataSourceDesc in the play list.
+ *
+ * @param index the index of next-to-be-played DataSourceDesc in the play list
+ * @throws IllegalArgumentException if the play list is null
+ * @throws NullPointerException if index is outside play list range
+ */
+ public abstract void setNextPlaylistItem(int index);
+
+ /**
+ * Gets the current index of play list.
+ *
+ * @return the index of the current DataSourceDesc in the play list
+ */
+ public abstract int getCurrentPlaylistItemIndex();
+
+ /**
+ * Specifies a playback looping mode. The source will not be played in looping mode.
+ */
+ public static final int LOOPING_MODE_NONE = 0;
+ /**
+ * Specifies a playback looping mode. The full list of source will be played in looping mode,
+ * and in the order specified in the play list.
+ */
+ public static final int LOOPING_MODE_FULL = 1;
+ /**
+ * Specifies a playback looping mode. The current DataSourceDesc will be played in looping mode.
+ */
+ public static final int LOOPING_MODE_SINGLE = 2;
+ /**
+ * Specifies a playback looping mode. The full list of source will be played in looping mode,
+ * and in a random order.
+ */
+ public static final int LOOPING_MODE_SHUFFLE = 3;
+
+ /** @hide */
+ @IntDef(
+ value = {
+ LOOPING_MODE_NONE,
+ LOOPING_MODE_FULL,
+ LOOPING_MODE_SINGLE,
+ LOOPING_MODE_SHUFFLE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface LoopingMode {}
+
+ /**
+ * Sets the looping mode of the play list.
+ * The mode shall be one of {@link #LOOPING_MODE_NONE}, {@link #LOOPING_MODE_FULL},
+ * {@link #LOOPING_MODE_SINGLE}, {@link #LOOPING_MODE_SHUFFLE}.
+ *
+ * @param mode the mode in which the play list will be played
+ * @throws IllegalArgumentException if mode is not supported
+ */
+ public abstract void setLoopingMode(@LoopingMode int mode);
+
+ /**
+ * Gets the looping mode of play list.
+ *
+ * @return the looping mode of the play list
+ */
+ public abstract int getLoopingMode();
+
+ /**
+ * Moves the DataSourceDesc at indexFrom in the play list to indexTo.
+ *
+ * @throws IllegalArgumentException if the play list is null
+ * @throws IndexOutOfBoundsException if indexFrom or indexTo is outside play list range
+ */
+ public abstract void movePlaylistItem(int indexFrom, int indexTo);
+
+ /**
+ * Removes the DataSourceDesc at index in the play list.
+ *
+ * If index is same as the current index of the play list, current DataSourceDesc
+ * will be stopped and playback moves to next source in the list.
+ *
+ * @return the removed DataSourceDesc at index in the play list
+ * @throws IllegalArgumentException if the play list is null
+ * @throws IndexOutOfBoundsException if index is outside play list range
+ */
+ public abstract DataSourceDesc removePlaylistItem(int index);
+
+ /**
+ * Inserts the DataSourceDesc to the play list at position index.
+ *
+ * This will not change the DataSourceDesc currently being played.
+ * If index is less than or equal to the current index of the play list,
+ * the current index of the play list will be incremented correspondingly.
+ *
+ * @param index the index you want to add dsd to the play list
+ * @param dsd the descriptor of data source you want to add to the play list
+ * @throws IndexOutOfBoundsException if index is outside play list range
+ * @throws NullPointerException if dsd is null
+ */
+ public abstract void addPlaylistItem(int index, DataSourceDesc dsd);
+
+ /**
+ * replaces the DataSourceDesc at index in the play list with given dsd.
+ *
+ * When index is same as the current index of the play list, the current source
+ * will be stopped and the new source will be played, except that if new
+ * and old source only differ on end position and current media position is
+ * smaller then the new end position.
+ *
+ * This will not change the DataSourceDesc currently being played.
+ * If index is less than or equal to the current index of the play list,
+ * the current index of the play list will be incremented correspondingly.
+ *
+ * @param index the index you want to add dsd to the play list
+ * @param dsd the descriptor of data source you want to add to the play list
+ * @throws IndexOutOfBoundsException if index is outside play list range
+ * @throws NullPointerException if dsd is null
+ */
+ public abstract DataSourceDesc editPlaylistItem(int index, DataSourceDesc dsd);
+
+ /**
+ * Prepares the player for playback, synchronously.
+ *
+ * After setting the datasource and the display surface, you need to either
+ * call prepare() or prepareAsync(). For files, it is OK to call prepare(),
+ * which blocks until MediaPlayer2 is ready for playback.
+ *
+ * @throws IOException if source can not be accessed
+ * @throws IllegalStateException if it is called in an invalid state
+ * @hide
+ */
+ public void prepare() throws IOException { }
+
+ /**
+ * Prepares the player for playback, asynchronously.
+ *
+ * After setting the datasource and the display surface, you need to
+ * call prepareAsync().
+ *
+ * @throws IllegalStateException if it is called in an invalid state
+ */
+ public abstract void prepareAsync();
+
+ /**
+ * Starts or resumes playback. If playback had previously been paused,
+ * playback will continue from where it was paused. If playback had
+ * been stopped, or never started before, playback will start at the
+ * beginning.
+ *
+ * @throws IllegalStateException if it is called in an invalid state
+ */
+ public abstract void play();
+
+ /**
+ * Stops playback after playback has been started or paused.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ * @hide
+ */
+ public void stop() { }
+
+ /**
+ * Pauses playback. Call play() to resume.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ */
+ public abstract void pause();
+
+ //--------------------------------------------------------------------------
+ // Explicit Routing
+ //--------------------
+
+ /**
+ * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
+ * the output from this MediaPlayer2.
+ * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source.
+ * If deviceInfo is null, default routing is restored.
+ * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
+ * does not correspond to a valid audio device.
+ */
+ @Override
+ public abstract boolean setPreferredDevice(AudioDeviceInfo deviceInfo);
+
+ /**
+ * Returns the selected output specified by {@link #setPreferredDevice}. Note that this
+ * is not guaranteed to correspond to the actual device being used for playback.
+ */
+ @Override
+ public abstract AudioDeviceInfo getPreferredDevice();
+
+ /**
+ * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer2
+ * Note: The query is only valid if the MediaPlayer2 is currently playing.
+ * If the player is not playing, the returned device can be null or correspond to previously
+ * selected device when the player was last active.
+ */
+ @Override
+ public abstract AudioDeviceInfo getRoutedDevice();
+
+ /**
+ * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+ * changes on this MediaPlayer2.
+ * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+ * notifications of rerouting events.
+ * @param handler Specifies the {@link Handler} object for the thread on which to execute
+ * the callback. If <code>null</code>, the handler on the main looper will be used.
+ */
+ @Override
+ public abstract void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
+ Handler handler);
+
+ /**
+ * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+ * to receive rerouting notifications.
+ * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+ * to remove.
+ */
+ @Override
+ public abstract void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener);
+
+ /**
+ * Set the low-level power management behavior for this MediaPlayer2.
+ *
+ * <p>This function has the MediaPlayer2 access the low-level power manager
+ * service to control the device's power usage while playing is occurring.
+ * The parameter is a combination of {@link android.os.PowerManager} wake flags.
+ * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK}
+ * permission.
+ * By default, no attempt is made to keep the device awake during playback.
+ *
+ * @param context the Context to use
+ * @param mode the power/wake mode to set
+ * @see android.os.PowerManager
+ * @hide
+ */
+ public abstract void setWakeMode(Context context, int mode);
+
+ /**
+ * Control whether we should use the attached SurfaceHolder to keep the
+ * screen on while video playback is occurring. This is the preferred
+ * method over {@link #setWakeMode} where possible, since it doesn't
+ * require that the application have permission for low-level wake lock
+ * access.
+ *
+ * @param screenOn Supply true to keep the screen on, false to allow it
+ * to turn off.
+ * @hide
+ */
+ public abstract void setScreenOnWhilePlaying(boolean screenOn);
+
+ /**
+ * Returns the width of the video.
+ *
+ * @return the width of the video, or 0 if there is no video,
+ * no display surface was set, or the width has not been determined
+ * yet. The {@code EventCallback} can be registered via
+ * {@link #registerEventCallback(Executor, EventCallback)} to provide a
+ * notification {@code EventCallback.onVideoSizeChanged} when the width is available.
+ */
+ public abstract int getVideoWidth();
+
+ /**
+ * Returns the height of the video.
+ *
+ * @return the height of the video, or 0 if there is no video,
+ * no display surface was set, or the height has not been determined
+ * yet. The {@code EventCallback} can be registered via
+ * {@link #registerEventCallback(Executor, EventCallback)} to provide a
+ * notification {@code EventCallback.onVideoSizeChanged} when the height is available.
+ */
+ public abstract int getVideoHeight();
+
+ /**
+ * Return Metrics data about the current player.
+ *
+ * @return a {@link PersistableBundle} containing the set of attributes and values
+ * available for the media being handled by this instance of MediaPlayer2
+ * The attributes are descibed in {@link MetricsConstants}.
+ *
+ * Additional vendor-specific fields may also be present in
+ * the return value.
+ */
+ public abstract PersistableBundle getMetrics();
+
+ /**
+ * Checks whether the MediaPlayer2 is playing.
+ *
+ * @return true if currently playing, false otherwise
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released.
+ */
+ public abstract boolean isPlaying();
+
+ /**
+ * Gets the current buffering management params used by the source component.
+ * Calling it only after {@code setDataSource} has been called.
+ * Each type of data source might have different set of default params.
+ *
+ * @return the current buffering management params used by the source component.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized, or {@code setDataSource} has not been called.
+ * @hide
+ */
+ @NonNull
+ public BufferingParams getBufferingParams() {
+ return new BufferingParams.Builder().build();
+ }
+
+ /**
+ * Sets buffering management params.
+ * The object sets its internal BufferingParams to the input, except that the input is
+ * invalid or not supported.
+ * Call it only after {@code setDataSource} has been called.
+ * The input is a hint to MediaPlayer2.
+ *
+ * @param params the buffering management params.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released, or {@code setDataSource} has not been called.
+ * @throws IllegalArgumentException if params is invalid or not supported.
+ * @hide
+ */
+ public void setBufferingParams(@NonNull BufferingParams params) { }
+
+ /**
+ * Change playback speed of audio by resampling the audio.
+ * <p>
+ * Specifies resampling as audio mode for variable rate playback, i.e.,
+ * resample the waveform based on the requested playback rate to get
+ * a new waveform, and play back the new waveform at the original sampling
+ * frequency.
+ * When rate is larger than 1.0, pitch becomes higher.
+ * When rate is smaller than 1.0, pitch becomes lower.
+ *
+ * @hide
+ */
+ public static final int PLAYBACK_RATE_AUDIO_MODE_RESAMPLE = 2;
+
+ /**
+ * Change playback speed of audio without changing its pitch.
+ * <p>
+ * Specifies time stretching as audio mode for variable rate playback.
+ * Time stretching changes the duration of the audio samples without
+ * affecting its pitch.
+ * <p>
+ * This mode is only supported for a limited range of playback speed factors,
+ * e.g. between 1/2x and 2x.
+ *
+ * @hide
+ */
+ public static final int PLAYBACK_RATE_AUDIO_MODE_STRETCH = 1;
+
+ /**
+ * Change playback speed of audio without changing its pitch, and
+ * possibly mute audio if time stretching is not supported for the playback
+ * speed.
+ * <p>
+ * Try to keep audio pitch when changing the playback rate, but allow the
+ * system to determine how to change audio playback if the rate is out
+ * of range.
+ *
+ * @hide
+ */
+ public static final int PLAYBACK_RATE_AUDIO_MODE_DEFAULT = 0;
+
+ /** @hide */
+ @IntDef(
+ value = {
+ PLAYBACK_RATE_AUDIO_MODE_DEFAULT,
+ PLAYBACK_RATE_AUDIO_MODE_STRETCH,
+ PLAYBACK_RATE_AUDIO_MODE_RESAMPLE,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PlaybackRateAudioMode {}
+
+ /**
+ * Sets playback rate and audio mode.
+ *
+ * @param rate the ratio between desired playback rate and normal one.
+ * @param audioMode audio playback mode. Must be one of the supported
+ * audio modes.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ * @throws IllegalArgumentException if audioMode is not supported.
+ *
+ * @hide
+ */
+ @NonNull
+ public PlaybackParams easyPlaybackParams(float rate, @PlaybackRateAudioMode int audioMode) {
+ return new PlaybackParams();
+ }
+
+ /**
+ * Sets playback rate using {@link PlaybackParams}. The object sets its internal
+ * PlaybackParams to the input, except that the object remembers previous speed
+ * when input speed is zero. This allows the object to resume at previous speed
+ * when play() is called. Calling it before the object is prepared does not change
+ * the object state. After the object is prepared, calling it with zero speed is
+ * equivalent to calling pause(). After the object is prepared, calling it with
+ * non-zero speed is equivalent to calling play().
+ *
+ * @param params the playback params.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released.
+ * @throws IllegalArgumentException if params is not supported.
+ */
+ public abstract void setPlaybackParams(@NonNull PlaybackParams params);
+
+ /**
+ * Gets the playback params, containing the current playback rate.
+ *
+ * @return the playback params.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ */
+ @NonNull
+ public abstract PlaybackParams getPlaybackParams();
+
+ /**
+ * Sets A/V sync mode.
+ *
+ * @param params the A/V sync params to apply
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ * @throws IllegalArgumentException if params are not supported.
+ */
+ public abstract void setSyncParams(@NonNull SyncParams params);
+
+ /**
+ * Gets the A/V sync mode.
+ *
+ * @return the A/V sync params
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ */
+ @NonNull
+ public abstract SyncParams getSyncParams();
+
+ /**
+ * Seek modes used in method seekTo(long, int) to move media position
+ * to a specified location.
+ *
+ * Do not change these mode values without updating their counterparts
+ * in include/media/IMediaSource.h!
+ */
+ /**
+ * This mode is used with {@link #seekTo(long, int)} to move media position to
+ * a sync (or key) frame associated with a data source that is located
+ * right before or at the given time.
+ *
+ * @see #seekTo(long, int)
+ */
+ public static final int SEEK_PREVIOUS_SYNC = 0x00;
+ /**
+ * This mode is used with {@link #seekTo(long, int)} to move media position to
+ * a sync (or key) frame associated with a data source that is located
+ * right after or at the given time.
+ *
+ * @see #seekTo(long, int)
+ */
+ public static final int SEEK_NEXT_SYNC = 0x01;
+ /**
+ * This mode is used with {@link #seekTo(long, int)} to move media position to
+ * a sync (or key) frame associated with a data source that is located
+ * closest to (in time) or at the given time.
+ *
+ * @see #seekTo(long, int)
+ */
+ public static final int SEEK_CLOSEST_SYNC = 0x02;
+ /**
+ * This mode is used with {@link #seekTo(long, int)} to move media position to
+ * a frame (not necessarily a key frame) associated with a data source that
+ * is located closest to or at the given time.
+ *
+ * @see #seekTo(long, int)
+ */
+ public static final int SEEK_CLOSEST = 0x03;
+
+ /** @hide */
+ @IntDef(
+ value = {
+ SEEK_PREVIOUS_SYNC,
+ SEEK_NEXT_SYNC,
+ SEEK_CLOSEST_SYNC,
+ SEEK_CLOSEST,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SeekMode {}
+
+ /**
+ * Moves the media to specified time position by considering the given mode.
+ * <p>
+ * When seekTo is finished, the user will be notified via OnSeekComplete supplied by the user.
+ * There is at most one active seekTo processed at any time. If there is a to-be-completed
+ * seekTo, new seekTo requests will be queued in such a way that only the last request
+ * is kept. When current seekTo is completed, the queued request will be processed if
+ * that request is different from just-finished seekTo operation, i.e., the requested
+ * position or mode is different.
+ *
+ * @param msec the offset in milliseconds from the start to seek to.
+ * When seeking to the given time position, there is no guarantee that the data source
+ * has a frame located at the position. When this happens, a frame nearby will be rendered.
+ * If msec is negative, time position zero will be used.
+ * If msec is larger than duration, duration will be used.
+ * @param mode the mode indicating where exactly to seek to.
+ * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame
+ * that has a timestamp earlier than or the same as msec. Use
+ * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame
+ * that has a timestamp later than or the same as msec. Use
+ * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame
+ * that has a timestamp closest to or the same as msec. Use
+ * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may
+ * or may not be a sync frame but is closest to or the same as msec.
+ * {@link #SEEK_CLOSEST} often has larger performance overhead compared
+ * to the other options if there is no sync frame located at msec.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized
+ * @throws IllegalArgumentException if the mode is invalid.
+ */
+ public abstract void seekTo(long msec, @SeekMode int mode);
+
+ /**
+ * Get current playback position as a {@link MediaTimestamp}.
+ * <p>
+ * The MediaTimestamp represents how the media time correlates to the system time in
+ * a linear fashion using an anchor and a clock rate. During regular playback, the media
+ * time moves fairly constantly (though the anchor frame may be rebased to a current
+ * system time, the linear correlation stays steady). Therefore, this method does not
+ * need to be called often.
+ * <p>
+ * To help users get current playback position, this method always anchors the timestamp
+ * to the current {@link System#nanoTime system time}, so
+ * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position.
+ *
+ * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp
+ * is available, e.g. because the media player has not been initialized.
+ *
+ * @see MediaTimestamp
+ */
+ @Nullable
+ public abstract MediaTimestamp getTimestamp();
+
+ /**
+ * Gets the current playback position.
+ *
+ * @return the current position in milliseconds
+ */
+ public abstract int getCurrentPosition();
+
+ /**
+ * Gets the duration of the file.
+ *
+ * @return the duration in milliseconds, if no duration is available
+ * (for example, if streaming live content), -1 is returned.
+ */
+ public abstract int getDuration();
+
+ /**
+ * Gets the media metadata.
+ *
+ * @param update_only controls whether the full set of available
+ * metadata is returned or just the set that changed since the
+ * last call. See {@see #METADATA_UPDATE_ONLY} and {@see
+ * #METADATA_ALL}.
+ *
+ * @param apply_filter if true only metadata that matches the
+ * filter is returned. See {@see #APPLY_METADATA_FILTER} and {@see
+ * #BYPASS_METADATA_FILTER}.
+ *
+ * @return The metadata, possibly empty. null if an error occured.
+ // FIXME: unhide.
+ * {@hide}
+ */
+ public Metadata getMetadata(final boolean update_only,
+ final boolean apply_filter) {
+ return null;
+ }
+
+ /**
+ * Set a filter for the metadata update notification and update
+ * retrieval. The caller provides 2 set of metadata keys, allowed
+ * and blocked. The blocked set always takes precedence over the
+ * allowed one.
+ * Metadata.MATCH_ALL and Metadata.MATCH_NONE are 2 sets available as
+ * shorthands to allow/block all or no metadata.
+ *
+ * By default, there is no filter set.
+ *
+ * @param allow Is the set of metadata the client is interested
+ * in receiving new notifications for.
+ * @param block Is the set of metadata the client is not interested
+ * in receiving new notifications for.
+ * @return The call status code.
+ *
+ // FIXME: unhide.
+ * {@hide}
+ */
+ public int setMetadataFilter(Set<Integer> allow, Set<Integer> block) {
+ return 0;
+ }
+
+ /**
+ * Set the MediaPlayer2 to start when this MediaPlayer2 finishes playback
+ * (i.e. reaches the end of the stream).
+ * The media framework will attempt to transition from this player to
+ * the next as seamlessly as possible. The next player can be set at
+ * any time before completion, but shall be after setDataSource has been
+ * called successfully. The next player must be prepared by the
+ * app, and the application should not call play() on it.
+ * The next MediaPlayer2 must be different from 'this'. An exception
+ * will be thrown if next == this.
+ * The application may call setNextMediaPlayer(null) to indicate no
+ * next player should be started at the end of playback.
+ * If the current player is looping, it will keep looping and the next
+ * player will not be started.
+ *
+ * @param next the player to start after this one completes playback.
+ *
+ * @hide
+ */
+ public void setNextMediaPlayer(MediaPlayer2 next) { }
+
+ /**
+ * Resets the MediaPlayer2 to its uninitialized state. After calling
+ * this method, you will have to initialize it again by setting the
+ * data source and calling prepareAsync().
+ */
+ public abstract void reset();
+
+ /**
+ * Set up a timer for {@link #TimeProvider}. {@link #TimeProvider} will be
+ * notified when the presentation time reaches (becomes greater than or equal to)
+ * the value specified.
+ *
+ * @param mediaTimeUs presentation time to get timed event callback at
+ * @hide
+ */
+ public void notifyAt(long mediaTimeUs) { }
+
+ /**
+ * Sets the audio attributes for this MediaPlayer2.
+ * See {@link AudioAttributes} for how to build and configure an instance of this class.
+ * You must call this method before {@link #prepareAsync()} in order
+ * for the audio attributes to become effective thereafter.
+ * @param attributes a non-null set of audio attributes
+ * @throws IllegalArgumentException if the attributes are null or invalid.
+ */
+ public abstract void setAudioAttributes(AudioAttributes attributes);
+
+ /**
+ * Sets the player to be looping or non-looping.
+ *
+ * @param looping whether to loop or not
+ * @hide
+ */
+ public void setLooping(boolean looping) { }
+
+ /**
+ * Checks whether the MediaPlayer2 is looping or non-looping.
+ *
+ * @return true if the MediaPlayer2 is currently looping, false otherwise
+ * @hide
+ */
+ public boolean isLooping() {
+ return false;
+ }
+
+ /**
+ * Sets the volume on this player.
+ * This API is recommended for balancing the output of audio streams
+ * within an application. Unless you are writing an application to
+ * control user settings, this API should be used in preference to
+ * {@link AudioManager#setStreamVolume(int, int, int)} which sets the volume of ALL streams of
+ * a particular type. Note that the passed volume values are raw scalars in range 0.0 to 1.0.
+ * UI controls should be scaled logarithmically.
+ *
+ * @param leftVolume left volume scalar
+ * @param rightVolume right volume scalar
+ */
+ /*
+ * FIXME: Merge this into javadoc comment above when setVolume(float) is not @hide.
+ * The single parameter form below is preferred if the channel volumes don't need
+ * to be set independently.
+ */
+ public abstract void setVolume(float leftVolume, float rightVolume);
+
+ /**
+ * Similar, excepts sets volume of all channels to same value.
+ * @hide
+ */
+ public void setVolume(float volume) { }
+
+ /**
+ * Sets the audio session ID.
+ *
+ * @param sessionId the audio session ID.
+ * The audio session ID is a system wide unique identifier for the audio stream played by
+ * this MediaPlayer2 instance.
+ * The primary use of the audio session ID is to associate audio effects to a particular
+ * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect,
+ * this effect will be applied only to the audio content of media players within the same
+ * audio session and not to the output mix.
+ * When created, a MediaPlayer2 instance automatically generates its own audio session ID.
+ * However, it is possible to force this player to be part of an already existing audio session
+ * by calling this method.
+ * This method must be called before one of the overloaded <code> setDataSource </code> methods.
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws IllegalArgumentException if the sessionId is invalid.
+ */
+ public abstract void setAudioSessionId(int sessionId);
+
+ /**
+ * Returns the audio session ID.
+ *
+ * @return the audio session ID. {@see #setAudioSessionId(int)}
+ * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was contructed.
+ */
+ public abstract int getAudioSessionId();
+
+ /**
+ * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
+ * effect which can be applied on any sound source that directs a certain amount of its
+ * energy to this effect. This amount is defined by setAuxEffectSendLevel().
+ * See {@link #setAuxEffectSendLevel(float)}.
+ * <p>After creating an auxiliary effect (e.g.
+ * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with
+ * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method
+ * to attach the player to the effect.
+ * <p>To detach the effect from the player, call this method with a null effect id.
+ * <p>This method must be called after one of the overloaded <code> setDataSource </code>
+ * methods.
+ * @param effectId system wide unique id of the effect to attach
+ */
+ public abstract void attachAuxEffect(int effectId);
+
+
+ /**
+ * Sets the send level of the player to the attached auxiliary effect.
+ * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0.
+ * <p>By default the send level is 0, so even if an effect is attached to the player
+ * this method must be called for the effect to be applied.
+ * <p>Note that the passed level value is a raw scalar. UI controls should be scaled
+ * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB,
+ * so an appropriate conversion from linear UI input x to level is:
+ * x == 0 -> level = 0
+ * 0 < x <= R -> level = 10^(72*(x-R)/20/R)
+ * @param level send level scalar
+ */
+ public abstract void setAuxEffectSendLevel(float level);
+
+ /**
+ * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata.
+ *
+ * @see android.media.MediaPlayer2#getTrackInfo
+ */
+ public abstract static class TrackInfo {
+ /**
+ * Gets the track type.
+ * @return TrackType which indicates if the track is video, audio, timed text.
+ */
+ public abstract int getTrackType();
+
+ /**
+ * Gets the language code of the track.
+ * @return a language code in either way of ISO-639-1 or ISO-639-2.
+ * When the language is unknown or could not be determined,
+ * ISO-639-2 language code, "und", is returned.
+ */
+ public abstract String getLanguage();
+
+ /**
+ * Gets the {@link MediaFormat} of the track. If the format is
+ * unknown or could not be determined, null is returned.
+ */
+ public abstract MediaFormat getFormat();
+
+ public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0;
+ public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
+ public static final int MEDIA_TRACK_TYPE_AUDIO = 2;
+
+ /** @hide */
+ public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3;
+
+ public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
+ public static final int MEDIA_TRACK_TYPE_METADATA = 5;
+
+ @Override
+ public abstract String toString();
+ };
+
+ /**
+ * Returns a List of track information.
+ *
+ * @return List of track info. The total number of tracks is the array length.
+ * Must be called again if an external timed text source has been added after
+ * addTimedTextSource method is called.
+ * @throws IllegalStateException if it is called in an invalid state.
+ */
+ public abstract List<TrackInfo> getTrackInfo();
+
+ /* Do not change these values without updating their counterparts
+ * in include/media/stagefright/MediaDefs.h and media/libstagefright/MediaDefs.cpp!
+ */
+ /**
+ * MIME type for SubRip (SRT) container. Used in addTimedTextSource APIs.
+ * @hide
+ */
+ public static final String MEDIA_MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
+
+ /**
+ * MIME type for WebVTT subtitle data.
+ * @hide
+ */
+ public static final String MEDIA_MIMETYPE_TEXT_VTT = "text/vtt";
+
+ /**
+ * MIME type for CEA-608 closed caption data.
+ * @hide
+ */
+ public static final String MEDIA_MIMETYPE_TEXT_CEA_608 = "text/cea-608";
+
+ /**
+ * MIME type for CEA-708 closed caption data.
+ * @hide
+ */
+ public static final String MEDIA_MIMETYPE_TEXT_CEA_708 = "text/cea-708";
+
+ /** @hide */
+ public void setSubtitleAnchor(
+ SubtitleController controller,
+ SubtitleController.Anchor anchor) { }
+
+ /** @hide */
+ @Override
+ public void onSubtitleTrackSelected(SubtitleTrack track) { }
+
+ /** @hide */
+ public void addSubtitleSource(InputStream is, MediaFormat format) { }
+
+ /* TODO: Limit the total number of external timed text source to a reasonable number.
+ */
+ /**
+ * Adds an external timed text source file.
+ *
+ * Currently supported format is SubRip with the file extension .srt, case insensitive.
+ * Note that a single external timed text source may contain multiple tracks in it.
+ * One can find the total number of available tracks using {@link #getTrackInfo()} to see what
+ * additional tracks become available after this method call.
+ *
+ * @param path The file path of external timed text source file.
+ * @param mimeType The mime type of the file. Must be one of the mime types listed above.
+ * @throws IOException if the file cannot be accessed or is corrupted.
+ * @throws IllegalArgumentException if the mimeType is not supported.
+ * @throws IllegalStateException if called in an invalid state.
+ * @hide
+ */
+ public void addTimedTextSource(String path, String mimeType) throws IOException { }
+
+ /**
+ * Adds an external timed text source file (Uri).
+ *
+ * Currently supported format is SubRip with the file extension .srt, case insensitive.
+ * Note that a single external timed text source may contain multiple tracks in it.
+ * One can find the total number of available tracks using {@link #getTrackInfo()} to see what
+ * additional tracks become available after this method call.
+ *
+ * @param context the Context to use when resolving the Uri
+ * @param uri the Content URI of the data you want to play
+ * @param mimeType The mime type of the file. Must be one of the mime types listed above.
+ * @throws IOException if the file cannot be accessed or is corrupted.
+ * @throws IllegalArgumentException if the mimeType is not supported.
+ * @throws IllegalStateException if called in an invalid state.
+ * @hide
+ */
+ public void addTimedTextSource(Context context, Uri uri, String mimeType) throws IOException { }
+
+ /**
+ * Adds an external timed text source file (FileDescriptor).
+ *
+ * It is the caller's responsibility to close the file descriptor.
+ * It is safe to do so as soon as this call returns.
+ *
+ * Currently supported format is SubRip. Note that a single external timed text source may
+ * contain multiple tracks in it. One can find the total number of available tracks
+ * using {@link #getTrackInfo()} to see what additional tracks become available
+ * after this method call.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @param mimeType The mime type of the file. Must be one of the mime types listed above.
+ * @throws IllegalArgumentException if the mimeType is not supported.
+ * @throws IllegalStateException if called in an invalid state.
+ * @hide
+ */
+ public void addTimedTextSource(FileDescriptor fd, String mimeType) { }
+
+ /**
+ * Adds an external timed text file (FileDescriptor).
+ *
+ * It is the caller's responsibility to close the file descriptor.
+ * It is safe to do so as soon as this call returns.
+ *
+ * Currently supported format is SubRip. Note that a single external timed text source may
+ * contain multiple tracks in it. One can find the total number of available tracks
+ * using {@link #getTrackInfo()} to see what additional tracks become available
+ * after this method call.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @param offset the offset into the file where the data to be played starts, in bytes
+ * @param length the length in bytes of the data to be played
+ * @param mime The mime type of the file. Must be one of the mime types listed above.
+ * @throws IllegalArgumentException if the mimeType is not supported.
+ * @throws IllegalStateException if called in an invalid state.
+ * @hide
+ */
+ public abstract void addTimedTextSource(FileDescriptor fd, long offset, long length, String mime);
+
+ /**
+ * Returns the index of the audio, video, or subtitle track currently selected for playback,
+ * The return value is an index into the array returned by {@link #getTrackInfo()}, and can
+ * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}.
+ *
+ * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
+ * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
+ * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
+ * @return index of the audio, video, or subtitle track currently selected for playback;
+ * a negative integer is returned when there is no selected track for {@code trackType} or
+ * when {@code trackType} is not one of audio, video, or subtitle.
+ * @throws IllegalStateException if called after {@link #close()}
+ *
+ * @see #getTrackInfo()
+ * @see #selectTrack(int)
+ * @see #deselectTrack(int)
+ */
+ public abstract int getSelectedTrack(int trackType);
+
+ /**
+ * Selects a track.
+ * <p>
+ * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception.
+ * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately.
+ * If a MediaPlayer2 is not in Started state, it just marks the track to be played.
+ * </p>
+ * <p>
+ * In any valid state, if it is called multiple times on the same type of track (ie. Video,
+ * Audio, Timed Text), the most recent one will be chosen.
+ * </p>
+ * <p>
+ * The first audio and video tracks are selected by default if available, even though
+ * this method is not called. However, no timed text track will be selected until
+ * this function is called.
+ * </p>
+ * <p>
+ * Currently, only timed text tracks or audio tracks can be selected via this method.
+ * In addition, the support for selecting an audio track at runtime is pretty limited
+ * in that an audio track can only be selected in the <em>Prepared</em> state.
+ * </p>
+ * @param index the index of the track to be selected. The valid range of the index
+ * is 0..total number of track - 1. The total number of tracks as well as the type of
+ * each individual track can be found by calling {@link #getTrackInfo()} method.
+ * @throws IllegalStateException if called in an invalid state.
+ *
+ * @see android.media.MediaPlayer2#getTrackInfo
+ */
+ public abstract void selectTrack(int index);
+
+ /**
+ * Deselect a track.
+ * <p>
+ * Currently, the track must be a timed text track and no audio or video tracks can be
+ * deselected. If the timed text track identified by index has not been
+ * selected before, it throws an exception.
+ * </p>
+ * @param index the index of the track to be deselected. The valid range of the index
+ * is 0..total number of tracks - 1. The total number of tracks as well as the type of
+ * each individual track can be found by calling {@link #getTrackInfo()} method.
+ * @throws IllegalStateException if called in an invalid state.
+ *
+ * @see android.media.MediaPlayer2#getTrackInfo
+ */
+ public abstract void deselectTrack(int index);
+
+ /**
+ * Sets the target UDP re-transmit endpoint for the low level player.
+ * Generally, the address portion of the endpoint is an IP multicast
+ * address, although a unicast address would be equally valid. When a valid
+ * retransmit endpoint has been set, the media player will not decode and
+ * render the media presentation locally. Instead, the player will attempt
+ * to re-multiplex its media data using the Android@Home RTP profile and
+ * re-transmit to the target endpoint. Receiver devices (which may be
+ * either the same as the transmitting device or different devices) may
+ * instantiate, prepare, and start a receiver player using a setDataSource
+ * URL of the form...
+ *
+ * aahRX://<multicastIP>:<port>
+ *
+ * to receive, decode and render the re-transmitted content.
+ *
+ * setRetransmitEndpoint may only be called before setDataSource has been
+ * called; while the player is in the Idle state.
+ *
+ * @param endpoint the address and UDP port of the re-transmission target or
+ * null if no re-transmission is to be performed.
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws IllegalArgumentException if the retransmit endpoint is supplied,
+ * but invalid.
+ *
+ * {@hide} pending API council
+ */
+ public void setRetransmitEndpoint(InetSocketAddress endpoint) { }
+
+ /**
+ * Releases the resources held by this {@code MediaPlayer2} object.
+ *
+ * It is considered good practice to call this method when you're
+ * done using the MediaPlayer2. In particular, whenever an Activity
+ * of an application is paused (its onPause() method is called),
+ * or stopped (its onStop() method is called), this method should be
+ * invoked to release the MediaPlayer2 object, unless the application
+ * has a special need to keep the object around. In addition to
+ * unnecessary resources (such as memory and instances of codecs)
+ * being held, failure to call this method immediately if a
+ * MediaPlayer2 object is no longer needed may also lead to
+ * continuous battery consumption for mobile devices, and playback
+ * failure for other applications if no multiple instances of the
+ * same codec are supported on a device. Even if multiple instances
+ * of the same codec are supported, some performance degradation
+ * may be expected when unnecessary multiple instances are used
+ * at the same time.
+ *
+ * {@code close()} may be safely called after a prior {@code close()}.
+ * This class implements the Java {@code AutoCloseable} interface and
+ * may be used with try-with-resources.
+ */
+ @Override
+ public abstract void close();
+
+ /** @hide */
+ public MediaTimeProvider getMediaTimeProvider() {
+ return null;
+ }
+
+ /**
+ * Interface definition for callbacks to be invoked when the player has the corresponding
+ * events.
+ */
+ public abstract static class EventCallback {
+ /**
+ * Called to update status in buffering a media source received through
+ * progressive downloading. The received buffering percentage
+ * indicates how much of the content has been buffered or played.
+ * For example a buffering update of 80 percent when half the content
+ * has already been played indicates that the next 30 percent of the
+ * content to play has been buffered.
+ *
+ * @param mp the MediaPlayer2 the update pertains to
+ * @param srcId the Id of this data source
+ * @param percent the percentage (0-100) of the content
+ * that has been buffered or played thus far
+ */
+ public void onBufferingUpdate(MediaPlayer2 mp, long srcId, int percent) { }
+
+ /**
+ * Called to indicate the video size
+ *
+ * The video size (width and height) could be 0 if there was no video,
+ * no display surface was set, or the value was not determined yet.
+ *
+ * @param mp the MediaPlayer2 associated with this callback
+ * @param srcId the Id of this data source
+ * @param width the width of the video
+ * @param height the height of the video
+ */
+ public void onVideoSizeChanged(MediaPlayer2 mp, long srcId, int width, int height) { }
+
+ /**
+ * Called to indicate an avaliable timed text
+ *
+ * @param mp the MediaPlayer2 associated with this callback
+ * @param srcId the Id of this data source
+ * @param text the timed text sample which contains the text
+ * needed to be displayed and the display format.
+ * @hide
+ */
+ public void onTimedText(MediaPlayer2 mp, long srcId, TimedText text) { }
+
+ /**
+ * Called to indicate avaliable timed metadata
+ * <p>
+ * This method will be called as timed metadata is extracted from the media,
+ * in the same order as it occurs in the media. The timing of this event is
+ * not controlled by the associated timestamp.
+ * <p>
+ * Currently only HTTP live streaming data URI's embedded with timed ID3 tags generates
+ * {@link TimedMetaData}.
+ *
+ * @see MediaPlayer2#selectTrack(int)
+ * @see MediaPlayer2.OnTimedMetaDataAvailableListener
+ * @see TimedMetaData
+ *
+ * @param mp the MediaPlayer2 associated with this callback
+ * @param srcId the Id of this data source
+ * @param data the timed metadata sample associated with this event
+ */
+ public void onTimedMetaDataAvailable(MediaPlayer2 mp, long srcId, TimedMetaData data) { }
+
+ /**
+ * Called to indicate an error.
+ *
+ * @param mp the MediaPlayer2 the error pertains to
+ * @param srcId the Id of this data source
+ * @param what the type of error that has occurred:
+ * <ul>
+ * <li>{@link #MEDIA_ERROR_UNKNOWN}
+ * </ul>
+ * @param extra an extra code, specific to the error. Typically
+ * implementation dependent.
+ * <ul>
+ * <li>{@link #MEDIA_ERROR_IO}
+ * <li>{@link #MEDIA_ERROR_MALFORMED}
+ * <li>{@link #MEDIA_ERROR_UNSUPPORTED}
+ * <li>{@link #MEDIA_ERROR_TIMED_OUT}
+ * <li><code>MEDIA_ERROR_SYSTEM (-2147483648)</code> - low-level system error.
+ * </ul>
+ */
+ public void onError(MediaPlayer2 mp, long srcId, int what, int extra) { }
+
+ /**
+ * Called to indicate an info or a warning.
+ *
+ * @param mp the MediaPlayer2 the info pertains to.
+ * @param srcId the Id of this data source
+ * @param what the type of info or warning.
+ * <ul>
+ * <li>{@link #MEDIA_INFO_UNKNOWN}
+ * <li>{@link #MEDIA_INFO_STARTED_AS_NEXT}
+ * <li>{@link #MEDIA_INFO_VIDEO_RENDERING_START}
+ * <li>{@link #MEDIA_INFO_AUDIO_RENDERING_START}
+ * <li>{@link #MEDIA_INFO_PLAYBACK_COMPLETE}
+ * <li>{@link #MEDIA_INFO_PLAYLIST_END}
+ * <li>{@link #MEDIA_INFO_PREPARED}
+ * <li>{@link #MEDIA_INFO_COMPLETE_CALL_PLAY}
+ * <li>{@link #MEDIA_INFO_COMPLETE_CALL_PAUSE}
+ * <li>{@link #MEDIA_INFO_COMPLETE_CALL_SEEK}
+ * <li>{@link #MEDIA_INFO_VIDEO_TRACK_LAGGING}
+ * <li>{@link #MEDIA_INFO_BUFFERING_START}
+ * <li>{@link #MEDIA_INFO_BUFFERING_END}
+ * <li><code>MEDIA_INFO_NETWORK_BANDWIDTH (703)</code> -
+ * bandwidth information is available (as <code>extra</code> kbps)
+ * <li>{@link #MEDIA_INFO_BAD_INTERLEAVING}
+ * <li>{@link #MEDIA_INFO_NOT_SEEKABLE}
+ * <li>{@link #MEDIA_INFO_METADATA_UPDATE}
+ * <li>{@link #MEDIA_INFO_UNSUPPORTED_SUBTITLE}
+ * <li>{@link #MEDIA_INFO_SUBTITLE_TIMED_OUT}
+ * </ul>
+ * @param extra an extra code, specific to the info. Typically
+ * implementation dependent.
+ */
+ public void onInfo(MediaPlayer2 mp, long srcId, int what, int extra) { }
+ }
+
+ /**
+ * Register a callback to be invoked when the media source is ready
+ * for playback.
+ *
+ * @param eventCallback the callback that will be run
+ * @param executor the executor through which the callback should be invoked
+ */
+ public abstract void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull EventCallback eventCallback);
+
+ /**
+ * Unregisters an {@link EventCallback}.
+ *
+ * @param callback an {@link EventCallback} to unregister
+ */
+ public abstract void unregisterEventCallback(EventCallback callback);
+
+ /**
+ * Interface definition of a callback to be invoked when a
+ * track has data available.
+ *
+ * @hide
+ */
+ public interface OnSubtitleDataListener
+ {
+ public void onSubtitleData(MediaPlayer2 mp, SubtitleData data);
+ }
+
+ /**
+ * Register a callback to be invoked when a track has data available.
+ *
+ * @param listener the callback that will be run
+ *
+ * @hide
+ */
+ public void setOnSubtitleDataListener(OnSubtitleDataListener listener) { }
+
+
+ /* Do not change these values without updating their counterparts
+ * in include/media/mediaplayer2.h!
+ */
+ /** Unspecified media player error.
+ * @see android.media.MediaPlayer2.EventCallback.onError
+ */
+ public static final int MEDIA_ERROR_UNKNOWN = 1;
+
+ /** The video is streamed and its container is not valid for progressive
+ * playback i.e the video's index (e.g moov atom) is not at the start of the
+ * file.
+ * @see android.media.MediaPlayer2.EventCallback.onError
+ */
+ public static final int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;
+
+ /** File or network related operation errors. */
+ public static final int MEDIA_ERROR_IO = -1004;
+ /** Bitstream is not conforming to the related coding standard or file spec. */
+ public static final int MEDIA_ERROR_MALFORMED = -1007;
+ /** Bitstream is conforming to the related coding standard or file spec, but
+ * the media framework does not support the feature. */
+ public static final int MEDIA_ERROR_UNSUPPORTED = -1010;
+ /** Some operation takes too long to complete, usually more than 3-5 seconds. */
+ public static final int MEDIA_ERROR_TIMED_OUT = -110;
+
+ /** Unspecified low-level system error. This value originated from UNKNOWN_ERROR in
+ * system/core/include/utils/Errors.h
+ * @see android.media.MediaPlayer2.EventCallback.onError
+ * @hide
+ */
+ public static final int MEDIA_ERROR_SYSTEM = -2147483648;
+
+
+ /* Do not change these values without updating their counterparts
+ * in include/media/mediaplayer2.h!
+ */
+ /** Unspecified media player info.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_UNKNOWN = 1;
+
+ /** The player switched to this datas source because it is the
+ * next-to-be-played in the play list.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_STARTED_AS_NEXT = 2;
+
+ /** The player just pushed the very first video frame for rendering.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3;
+
+ /** The player just rendered the very first audio sample.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_AUDIO_RENDERING_START = 4;
+
+ /** The player just completed the playback of this data source.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_PLAYBACK_COMPLETE = 5;
+
+ /** The player just completed the playback of the full play list.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_PLAYLIST_END = 6;
+
+ /** The player just prepared a data source.
+ * This also serves as call completion notification for {@link #prepareAsync()}.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_PREPARED = 100;
+
+ /** The player just completed a call {@link #play()}.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_COMPLETE_CALL_PLAY = 101;
+
+ /** The player just completed a call {@link #pause()}.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_COMPLETE_CALL_PAUSE = 102;
+
+ /** The player just completed a call {@link #seekTo(long, int)}.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_COMPLETE_CALL_SEEK = 103;
+
+ /** The video is too complex for the decoder: it can't decode frames fast
+ * enough. Possibly only the audio plays fine at this stage.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;
+
+ /** MediaPlayer2 is temporarily pausing playback internally in order to
+ * buffer more data.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_BUFFERING_START = 701;
+
+ /** MediaPlayer2 is resuming playback after filling buffers.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_BUFFERING_END = 702;
+
+ /** Estimated network bandwidth information (kbps) is available; currently this event fires
+ * simultaneously as {@link #MEDIA_INFO_BUFFERING_START} and {@link #MEDIA_INFO_BUFFERING_END}
+ * when playing network files.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ * @hide
+ */
+ public static final int MEDIA_INFO_NETWORK_BANDWIDTH = 703;
+
+ /** Bad interleaving means that a media has been improperly interleaved or
+ * not interleaved at all, e.g has all the video samples first then all the
+ * audio ones. Video is playing but a lot of disk seeks may be happening.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_BAD_INTERLEAVING = 800;
+
+ /** The media cannot be seeked (e.g live stream)
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_NOT_SEEKABLE = 801;
+
+ /** A new set of metadata is available.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_METADATA_UPDATE = 802;
+
+ /** A new set of external-only metadata is available. Used by
+ * JAVA framework to avoid triggering track scanning.
+ * @hide
+ */
+ public static final int MEDIA_INFO_EXTERNAL_METADATA_UPDATE = 803;
+
+ /** Informs that audio is not playing. Note that playback of the video
+ * is not interrupted.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_AUDIO_NOT_PLAYING = 804;
+
+ /** Informs that video is not playing. Note that playback of the audio
+ * is not interrupted.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_VIDEO_NOT_PLAYING = 805;
+
+ /** Failed to handle timed text track properly.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ *
+ * {@hide}
+ */
+ public static final int MEDIA_INFO_TIMED_TEXT_ERROR = 900;
+
+ /** Subtitle track was not supported by the media framework.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;
+
+ /** Reading the subtitle track takes too long.
+ * @see android.media.MediaPlayer2.EventCallback.onInfo
+ */
+ public static final int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;
+
+
+ // Modular DRM begin
+
+ /**
+ * Interface definition of a callback to be invoked when the app
+ * can do DRM configuration (get/set properties) before the session
+ * is opened. This facilitates configuration of the properties, like
+ * 'securityLevel', which has to be set after DRM scheme creation but
+ * before the DRM session is opened.
+ *
+ * The only allowed DRM calls in this listener are {@code getDrmPropertyString}
+ * and {@code setDrmPropertyString}.
+ */
+ public interface OnDrmConfigHelper
+ {
+ /**
+ * Called to give the app the opportunity to configure DRM before the session is created
+ *
+ * @param mp the {@code MediaPlayer2} associated with this callback
+ */
+ public void onDrmConfig(MediaPlayer2 mp);
+ }
+
+ /**
+ * Register a callback to be invoked for configuration of the DRM object before
+ * the session is created.
+ * The callback will be invoked synchronously during the execution
+ * of {@link #prepareDrm(UUID uuid)}.
+ *
+ * @param listener the callback that will be run
+ */
+ public abstract void setOnDrmConfigHelper(OnDrmConfigHelper listener);
+
+ /**
+ * Interface definition for callbacks to be invoked when the player has the corresponding
+ * DRM events.
+ */
+ public abstract static class DrmEventCallback {
+ /**
+ * Called to indicate DRM info is available
+ *
+ * @param mp the {@code MediaPlayer2} associated with this callback
+ * @param drmInfo DRM info of the source including PSSH, and subset
+ * of crypto schemes supported by this device
+ */
+ public void onDrmInfo(MediaPlayer2 mp, DrmInfo drmInfo) { }
+
+ /**
+ * Called to notify the client that {@code prepareDrm} is finished and ready for key request/response.
+ *
+ * @param mp the {@code MediaPlayer2} associated with this callback
+ * @param status the result of DRM preparation which can be
+ * {@link #PREPARE_DRM_STATUS_SUCCESS},
+ * {@link #PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR},
+ * {@link #PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR}, or
+ * {@link #PREPARE_DRM_STATUS_PREPARATION_ERROR}.
+ */
+ public void onDrmPrepared(MediaPlayer2 mp, @PrepareDrmStatusCode int status) { }
+
+ }
+
+ /**
+ * Register a callback to be invoked when the media source is ready
+ * for playback.
+ *
+ * @param eventCallback the callback that will be run
+ * @param executor the executor through which the callback should be invoked
+ */
+ public abstract void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull DrmEventCallback eventCallback);
+
+ /**
+ * Unregisters a {@link DrmEventCallback}.
+ *
+ * @param callback a {@link DrmEventCallback} to unregister
+ */
+ public abstract void unregisterDrmEventCallback(DrmEventCallback callback);
+
+ /**
+ * The status codes for {@link DrmEventCallback#onDrmPrepared} listener.
+ * <p>
+ *
+ * DRM preparation has succeeded.
+ */
+ public static final int PREPARE_DRM_STATUS_SUCCESS = 0;
+
+ /**
+ * The device required DRM provisioning but couldn't reach the provisioning server.
+ */
+ public static final int PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR = 1;
+
+ /**
+ * The device required DRM provisioning but the provisioning server denied the request.
+ */
+ public static final int PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR = 2;
+
+ /**
+ * The DRM preparation has failed .
+ */
+ public static final int PREPARE_DRM_STATUS_PREPARATION_ERROR = 3;
+
+
+ /** @hide */
+ @IntDef({
+ PREPARE_DRM_STATUS_SUCCESS,
+ PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR,
+ PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR,
+ PREPARE_DRM_STATUS_PREPARATION_ERROR,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PrepareDrmStatusCode {}
+
+ /**
+ * Retrieves the DRM Info associated with the current source
+ *
+ * @throws IllegalStateException if called before being prepared
+ */
+ public abstract DrmInfo getDrmInfo();
+
+ /**
+ * Prepares the DRM for the current source
+ * <p>
+ * If {@code OnDrmConfigHelper} is registered, it will be called during
+ * preparation to allow configuration of the DRM properties before opening the
+ * DRM session. Note that the callback is called synchronously in the thread that called
+ * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString}
+ * and {@code setDrmPropertyString} calls and refrain from any lengthy operation.
+ * <p>
+ * If the device has not been provisioned before, this call also provisions the device
+ * which involves accessing the provisioning server and can take a variable time to
+ * complete depending on the network connectivity.
+ * If {@code OnDrmPreparedListener} is registered, prepareDrm() runs in non-blocking
+ * mode by launching the provisioning in the background and returning. The listener
+ * will be called when provisioning and preparation has finished. If a
+ * {@code OnDrmPreparedListener} is not registered, prepareDrm() waits till provisioning
+ * and preparation has finished, i.e., runs in blocking mode.
+ * <p>
+ * If {@code OnDrmPreparedListener} is registered, it is called to indicate the DRM
+ * session being ready. The application should not make any assumption about its call
+ * sequence (e.g., before or after prepareDrm returns), or the thread context that will
+ * execute the listener (unless the listener is registered with a handler thread).
+ * <p>
+ *
+ * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
+ * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}.
+ *
+ * @throws IllegalStateException if called before being prepared or the DRM was
+ * prepared already
+ * @throws UnsupportedSchemeException if the crypto scheme is not supported
+ * @throws ResourceBusyException if required DRM resources are in use
+ * @throws ProvisioningNetworkErrorException if provisioning is required but failed due to a
+ * network error
+ * @throws ProvisioningServerErrorException if provisioning is required but failed due to
+ * the request denied by the provisioning server
+ */
+ public abstract void prepareDrm(@NonNull UUID uuid)
+ throws UnsupportedSchemeException, ResourceBusyException,
+ ProvisioningNetworkErrorException, ProvisioningServerErrorException;
+
+ /**
+ * Releases the DRM session
+ * <p>
+ * The player has to have an active DRM session and be in stopped, or prepared
+ * state before this call is made.
+ * A {@code reset()} call will release the DRM session implicitly.
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session to release
+ */
+ public abstract void releaseDrm() throws NoDrmSchemeException;
+
+ /**
+ * A key request/response exchange occurs between the app and a license server
+ * to obtain or release keys used to decrypt encrypted content.
+ * <p>
+ * getKeyRequest() is used to obtain an opaque key request byte array that is
+ * delivered to the license server. The opaque key request byte array is returned
+ * in KeyRequest.data. The recommended URL to deliver the key request to is
+ * returned in KeyRequest.defaultUrl.
+ * <p>
+ * After the app has received the key request response from the server,
+ * it should deliver to the response to the DRM engine plugin using the method
+ * {@link #provideKeyResponse}.
+ *
+ * @param keySetId is the key-set identifier of the offline keys being released when keyType is
+ * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
+ * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
+ *
+ * @param initData is the container-specific initialization data when the keyType is
+ * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is
+ * interpreted based on the mime type provided in the mimeType parameter. It could
+ * contain, for example, the content ID, key ID or other data obtained from the content
+ * metadata that is required in generating the key request.
+ * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null.
+ *
+ * @param mimeType identifies the mime type of the content
+ *
+ * @param keyType specifies the type of the request. The request may be to acquire
+ * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content
+ * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired
+ * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId.
+ *
+ * @param optionalParameters are included in the key request message to
+ * allow a client application to provide additional message parameters to the server.
+ * This may be {@code null} if no additional parameters are to be sent.
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session
+ */
+ @NonNull
+ public abstract MediaDrm.KeyRequest getKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData,
+ @Nullable String mimeType, @MediaDrm.KeyType int keyType,
+ @Nullable Map<String, String> optionalParameters)
+ throws NoDrmSchemeException;
+
+ /**
+ * A key response is received from the license server by the app, then it is
+ * provided to the DRM engine plugin using provideKeyResponse. When the
+ * response is for an offline key request, a key-set identifier is returned that
+ * can be used to later restore the keys to a new session with the method
+ * {@ link # restoreKeys}.
+ * When the response is for a streaming or release request, null is returned.
+ *
+ * @param keySetId When the response is for a release request, keySetId identifies
+ * the saved key associated with the release request (i.e., the same keySetId
+ * passed to the earlier {@ link # getKeyRequest} call. It MUST be null when the
+ * response is for either streaming or offline key requests.
+ *
+ * @param response the byte array response from the server
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session
+ * @throws DeniedByServerException if the response indicates that the
+ * server rejected the request
+ */
+ public abstract byte[] provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
+ throws NoDrmSchemeException, DeniedByServerException;
+
+ /**
+ * Restore persisted offline keys into a new session. keySetId identifies the
+ * keys to load, obtained from a prior call to {@link #provideKeyResponse}.
+ *
+ * @param keySetId identifies the saved key set to restore
+ */
+ public abstract void restoreKeys(@NonNull byte[] keySetId)
+ throws NoDrmSchemeException;
+
+ /**
+ * Read a DRM engine plugin String property value, given the property name string.
+ * <p>
+ * @param propertyName the property name
+ *
+ * Standard fields names are:
+ * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
+ * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
+ */
+ @NonNull
+ public abstract String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName)
+ throws NoDrmSchemeException;
+
+ /**
+ * Set a DRM engine plugin String property value.
+ * <p>
+ * @param propertyName the property name
+ * @param value the property value
+ *
+ * Standard fields names are:
+ * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
+ * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
+ */
+ public abstract void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName,
+ @NonNull String value)
+ throws NoDrmSchemeException;
+
+ /**
+ * Encapsulates the DRM properties of the source.
+ */
+ public abstract static class DrmInfo {
+ /**
+ * Returns the PSSH info of the data source for each supported DRM scheme.
+ */
+ public abstract Map<UUID, byte[]> getPssh();
+
+ /**
+ * Returns the intersection of the data source and the device DRM schemes.
+ * It effectively identifies the subset of the source's DRM schemes which
+ * are supported by the device too.
+ */
+ public abstract List<UUID> getSupportedSchemes();
+ }; // DrmInfo
+
+ /**
+ * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
+ * Extends MediaDrm.MediaDrmException
+ */
+ public abstract static class NoDrmSchemeException extends MediaDrmException {
+ protected NoDrmSchemeException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Thrown when the device requires DRM provisioning but the provisioning attempt has
+ * failed due to a network error (Internet reachability, timeout, etc.).
+ * Extends MediaDrm.MediaDrmException
+ */
+ public abstract static class ProvisioningNetworkErrorException extends MediaDrmException {
+ protected ProvisioningNetworkErrorException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Thrown when the device requires DRM provisioning but the provisioning attempt has
+ * failed due to the provisioning server denying the request.
+ * Extends MediaDrm.MediaDrmException
+ */
+ public abstract static class ProvisioningServerErrorException extends MediaDrmException {
+ protected ProvisioningServerErrorException(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ public static final class MetricsConstants {
+ private MetricsConstants() {}
+
+ /**
+ * Key to extract the MIME type of the video track
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is a String.
+ */
+ public static final String MIME_TYPE_VIDEO = "android.media.mediaplayer.video.mime";
+
+ /**
+ * Key to extract the codec being used to decode the video track
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is a String.
+ */
+ public static final String CODEC_VIDEO = "android.media.mediaplayer.video.codec";
+
+ /**
+ * Key to extract the width (in pixels) of the video track
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is an integer.
+ */
+ public static final String WIDTH = "android.media.mediaplayer.width";
+
+ /**
+ * Key to extract the height (in pixels) of the video track
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is an integer.
+ */
+ public static final String HEIGHT = "android.media.mediaplayer.height";
+
+ /**
+ * Key to extract the count of video frames played
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is an integer.
+ */
+ public static final String FRAMES = "android.media.mediaplayer.frames";
+
+ /**
+ * Key to extract the count of video frames dropped
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is an integer.
+ */
+ public static final String FRAMES_DROPPED = "android.media.mediaplayer.dropped";
+
+ /**
+ * Key to extract the MIME type of the audio track
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is a String.
+ */
+ public static final String MIME_TYPE_AUDIO = "android.media.mediaplayer.audio.mime";
+
+ /**
+ * Key to extract the codec being used to decode the audio track
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is a String.
+ */
+ public static final String CODEC_AUDIO = "android.media.mediaplayer.audio.codec";
+
+ /**
+ * Key to extract the duration (in milliseconds) of the
+ * media being played
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is a long.
+ */
+ public static final String DURATION = "android.media.mediaplayer.durationMs";
+
+ /**
+ * Key to extract the playing time (in milliseconds) of the
+ * media being played
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is a long.
+ */
+ public static final String PLAYING = "android.media.mediaplayer.playingMs";
+
+ /**
+ * Key to extract the count of errors encountered while
+ * playing the media
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is an integer.
+ */
+ public static final String ERRORS = "android.media.mediaplayer.err";
+
+ /**
+ * Key to extract an (optional) error code detected while
+ * playing the media
+ * from the {@link MediaPlayer2#getMetrics} return value.
+ * The value is an integer.
+ */
+ public static final String ERROR_CODE = "android.media.mediaplayer.errcode";
+
+ }
+}
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
new file mode 100644
index 0000000..86a285c
--- /dev/null
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -0,0 +1,4899 @@
+/*
+ * Copyright 2018 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;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Log;
+import android.util.Pair;
+import android.util.ArrayMap;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.widget.VideoView;
+import android.graphics.SurfaceTexture;
+import android.media.AudioManager;
+import android.media.MediaDrm;
+import android.media.MediaFormat;
+import android.media.MediaPlayer2;
+import android.media.MediaTimeProvider;
+import android.media.PlaybackParams;
+import android.media.SubtitleController;
+import android.media.SubtitleController.Anchor;
+import android.media.SubtitleData;
+import android.media.SubtitleTrack.RenderingWidget;
+import android.media.SyncParams;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import dalvik.system.CloseGuard;
+
+import libcore.io.IoBridge;
+import libcore.io.Streams;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.AutoCloseable;
+import java.lang.Runnable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.net.CookieHandler;
+import java.net.CookieManager;
+import java.net.HttpCookie;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.URL;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.concurrent.Executor;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.UUID;
+import java.util.Vector;
+
+
+/**
+ * MediaPlayer2 class can be used to control playback
+ * of audio/video files and streams. An example on how to use the methods in
+ * this class can be found in {@link android.widget.VideoView}.
+ *
+ * <p>Topics covered here are:
+ * <ol>
+ * <li><a href="#StateDiagram">State Diagram</a>
+ * <li><a href="#Valid_and_Invalid_States">Valid and Invalid States</a>
+ * <li><a href="#Permissions">Permissions</a>
+ * <li><a href="#Callbacks">Register informational and error callbacks</a>
+ * </ol>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about how to use MediaPlayer2, read the
+ * <a href="{@docRoot}guide/topics/media/mediaplayer.html">Media Playback</a> developer guide.</p>
+ * </div>
+ *
+ * <a name="StateDiagram"></a>
+ * <h3>State Diagram</h3>
+ *
+ * <p>Playback control of audio/video files and streams is managed as a state
+ * machine. The following diagram shows the life cycle and the states of a
+ * MediaPlayer2 object driven by the supported playback control operations.
+ * The ovals represent the states a MediaPlayer2 object may reside
+ * in. The arcs represent the playback control operations that drive the object
+ * state transition. There are two types of arcs. The arcs with a single arrow
+ * head represent synchronous method calls, while those with
+ * a double arrow head represent asynchronous method calls.</p>
+ *
+ * <p><img src="../../../images/mediaplayer_state_diagram.gif"
+ * alt="MediaPlayer State diagram"
+ * border="0" /></p>
+ *
+ * <p>From this state diagram, one can see that a MediaPlayer2 object has the
+ * following states:</p>
+ * <ul>
+ * <li>When a MediaPlayer2 object is just created using <code>new</code> or
+ * after {@link #reset()} is called, it is in the <em>Idle</em> state; and after
+ * {@link #close()} is called, it is in the <em>End</em> state. Between these
+ * two states is the life cycle of the MediaPlayer2 object.
+ * <ul>
+ * <li>There is a subtle but important difference between a newly constructed
+ * MediaPlayer2 object and the MediaPlayer2 object after {@link #reset()}
+ * is called. It is a programming error to invoke methods such
+ * as {@link #getCurrentPosition()},
+ * {@link #getDuration()}, {@link #getVideoHeight()},
+ * {@link #getVideoWidth()}, {@link #setAudioAttributes(AudioAttributes)},
+ * {@link #setLooping(boolean)},
+ * {@link #setVolume(float, float)}, {@link #pause()}, {@link #play()},
+ * {@link #seekTo(long, int)}, {@link #prepare()} or
+ * {@link #prepareAsync()} in the <em>Idle</em> state for both cases. If any of these
+ * methods is called right after a MediaPlayer2 object is constructed,
+ * the user supplied callback method OnErrorListener.onError() won't be
+ * called by the internal player engine and the object state remains
+ * unchanged; but if these methods are called right after {@link #reset()},
+ * the user supplied callback method OnErrorListener.onError() will be
+ * invoked by the internal player engine and the object will be
+ * transfered to the <em>Error</em> state. </li>
+ * <li>It is also recommended that once
+ * a MediaPlayer2 object is no longer being used, call {@link #close()} immediately
+ * so that resources used by the internal player engine associated with the
+ * MediaPlayer2 object can be released immediately. Resource may include
+ * singleton resources such as hardware acceleration components and
+ * failure to call {@link #close()} may cause subsequent instances of
+ * MediaPlayer2 objects to fallback to software implementations or fail
+ * altogether. Once the MediaPlayer2
+ * object is in the <em>End</em> state, it can no longer be used and
+ * there is no way to bring it back to any other state. </li>
+ * <li>Furthermore,
+ * the MediaPlayer2 objects created using <code>new</code> is in the
+ * <em>Idle</em> state.
+ * </li>
+ * </ul>
+ * </li>
+ * <li>In general, some playback control operation may fail due to various
+ * reasons, such as unsupported audio/video format, poorly interleaved
+ * audio/video, resolution too high, streaming timeout, and the like.
+ * Thus, error reporting and recovery is an important concern under
+ * these circumstances. Sometimes, due to programming errors, invoking a playback
+ * control operation in an invalid state may also occur. Under all these
+ * error conditions, the internal player engine invokes a user supplied
+ * EventCallback.onError() method if an EventCallback has been
+ * registered beforehand via
+ * {@link #registerEventCallback(Executor, EventCallback)}.
+ * <ul>
+ * <li>It is important to note that once an error occurs, the
+ * MediaPlayer2 object enters the <em>Error</em> state (except as noted
+ * above), even if an error listener has not been registered by the application.</li>
+ * <li>In order to reuse a MediaPlayer2 object that is in the <em>
+ * Error</em> state and recover from the error,
+ * {@link #reset()} can be called to restore the object to its <em>Idle</em>
+ * state.</li>
+ * <li>It is good programming practice to have your application
+ * register a OnErrorListener to look out for error notifications from
+ * the internal player engine.</li>
+ * <li>IllegalStateException is
+ * thrown to prevent programming errors such as calling {@link #prepare()},
+ * {@link #prepareAsync()}, {@link #setDataSource(DataSourceDesc)}, or
+ * {@code setPlaylist} methods in an invalid state. </li>
+ * </ul>
+ * </li>
+ * <li>Calling
+ * {@link #setDataSource(DataSourceDesc)}, or
+ * {@code setPlaylist} transfers a
+ * MediaPlayer2 object in the <em>Idle</em> state to the
+ * <em>Initialized</em> state.
+ * <ul>
+ * <li>An IllegalStateException is thrown if
+ * setDataSource() or setPlaylist() is called in any other state.</li>
+ * <li>It is good programming
+ * practice to always look out for <code>IllegalArgumentException</code>
+ * and <code>IOException</code> that may be thrown from
+ * <code>setDataSource</code> and <code>setPlaylist</code> methods.</li>
+ * </ul>
+ * </li>
+ * <li>A MediaPlayer2 object must first enter the <em>Prepared</em> state
+ * before playback can be started.
+ * <ul>
+ * <li>There are two ways (synchronous vs.
+ * asynchronous) that the <em>Prepared</em> state can be reached:
+ * either a call to {@link #prepare()} (synchronous) which
+ * transfers the object to the <em>Prepared</em> state once the method call
+ * returns, or a call to {@link #prepareAsync()} (asynchronous) which
+ * first transfers the object to the <em>Preparing</em> state after the
+ * call returns (which occurs almost right way) while the internal
+ * player engine continues working on the rest of preparation work
+ * until the preparation work completes. When the preparation completes or when {@link #prepare()} call returns,
+ * the internal player engine then calls a user supplied callback method,
+ * onPrepared() of the EventCallback interface, if an
+ * EventCallback is registered beforehand via {@link
+ * #registerEventCallback(Executor, EventCallback)}.</li>
+ * <li>It is important to note that
+ * the <em>Preparing</em> state is a transient state, and the behavior
+ * of calling any method with side effect while a MediaPlayer2 object is
+ * in the <em>Preparing</em> state is undefined.</li>
+ * <li>An IllegalStateException is
+ * thrown if {@link #prepare()} or {@link #prepareAsync()} is called in
+ * any other state.</li>
+ * <li>While in the <em>Prepared</em> state, properties
+ * such as audio/sound volume, screenOnWhilePlaying, looping can be
+ * adjusted by invoking the corresponding set methods.</li>
+ * </ul>
+ * </li>
+ * <li>To start the playback, {@link #play()} must be called. After
+ * {@link #play()} returns successfully, the MediaPlayer2 object is in the
+ * <em>Started</em> state. {@link #isPlaying()} can be called to test
+ * whether the MediaPlayer2 object is in the <em>Started</em> state.
+ * <ul>
+ * <li>While in the <em>Started</em> state, the internal player engine calls
+ * a user supplied EventCallback.onBufferingUpdate() callback
+ * method if an EventCallback has been registered beforehand
+ * via {@link #registerEventCallback(Executor, EventCallback)}.
+ * This callback allows applications to keep track of the buffering status
+ * while streaming audio/video.</li>
+ * <li>Calling {@link #play()} has not effect
+ * on a MediaPlayer2 object that is already in the <em>Started</em> state.</li>
+ * </ul>
+ * </li>
+ * <li>Playback can be paused and stopped, and the current playback position
+ * can be adjusted. Playback can be paused via {@link #pause()}. When the call to
+ * {@link #pause()} returns, the MediaPlayer2 object enters the
+ * <em>Paused</em> state. Note that the transition from the <em>Started</em>
+ * state to the <em>Paused</em> state and vice versa happens
+ * asynchronously in the player engine. It may take some time before
+ * the state is updated in calls to {@link #isPlaying()}, and it can be
+ * a number of seconds in the case of streamed content.
+ * <ul>
+ * <li>Calling {@link #play()} to resume playback for a paused
+ * MediaPlayer2 object, and the resumed playback
+ * position is the same as where it was paused. When the call to
+ * {@link #play()} returns, the paused MediaPlayer2 object goes back to
+ * the <em>Started</em> state.</li>
+ * <li>Calling {@link #pause()} has no effect on
+ * a MediaPlayer2 object that is already in the <em>Paused</em> state.</li>
+ * </ul>
+ * </li>
+ * <li>The playback position can be adjusted with a call to
+ * {@link #seekTo(long, int)}.
+ * <ul>
+ * <li>Although the asynchronuous {@link #seekTo(long, int)}
+ * call returns right away, the actual seek operation may take a while to
+ * finish, especially for audio/video being streamed. When the actual
+ * seek operation completes, the internal player engine calls a user
+ * supplied EventCallback.onSeekComplete() if an EventCallback
+ * has been registered beforehand via
+ * {@link #registerEventCallback(Executor, EventCallback)}.</li>
+ * <li>Please
+ * note that {@link #seekTo(long, int)} can also be called in the other states,
+ * such as <em>Prepared</em>, <em>Paused</em> and <em>PlaybackCompleted
+ * </em> state. When {@link #seekTo(long, int)} is called in those states,
+ * one video frame will be displayed if the stream has video and the requested
+ * position is valid.
+ * </li>
+ * <li>Furthermore, the actual current playback position
+ * can be retrieved with a call to {@link #getCurrentPosition()}, which
+ * is helpful for applications such as a Music player that need to keep
+ * track of the playback progress.</li>
+ * </ul>
+ * </li>
+ * <li>When the playback reaches the end of stream, the playback completes.
+ * <ul>
+ * <li>If the looping mode was being set to <var>true</var>with
+ * {@link #setLooping(boolean)}, the MediaPlayer2 object shall remain in
+ * the <em>Started</em> state.</li>
+ * <li>If the looping mode was set to <var>false
+ * </var>, the player engine calls a user supplied callback method,
+ * EventCallback.onCompletion(), if an EventCallback is registered
+ * beforehand via {@link #registerEventCallback(Executor, EventCallback)}.
+ * The invoke of the callback signals that the object is now in the <em>
+ * PlaybackCompleted</em> state.</li>
+ * <li>While in the <em>PlaybackCompleted</em>
+ * state, calling {@link #play()} can restart the playback from the
+ * beginning of the audio/video source.</li>
+ * </ul>
+ *
+ *
+ * <a name="Valid_and_Invalid_States"></a>
+ * <h3>Valid and invalid states</h3>
+ *
+ * <table border="0" cellspacing="0" cellpadding="0">
+ * <tr><td>Method Name </p></td>
+ * <td>Valid Sates </p></td>
+ * <td>Invalid States </p></td>
+ * <td>Comments </p></td></tr>
+ * <tr><td>attachAuxEffect </p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td>
+ * <td>{Idle, Error} </p></td>
+ * <td>This method must be called after setDataSource or setPlaylist.
+ * Calling it does not change the object state. </p></td></tr>
+ * <tr><td>getAudioSessionId </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>getCurrentPosition </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted} </p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change the
+ * state. Calling this method in an invalid state transfers the object
+ * to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>getDuration </p></td>
+ * <td>{Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td>
+ * <td>{Idle, Initialized, Error} </p></td>
+ * <td>Successful invoke of this method in a valid state does not change the
+ * state. Calling this method in an invalid state transfers the object
+ * to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>getVideoHeight </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change the
+ * state. Calling this method in an invalid state transfers the object
+ * to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>getVideoWidth </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an invalid state transfers the
+ * object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>isPlaying </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an invalid state transfers the
+ * object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>pause </p></td>
+ * <td>{Started, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Prepared, Stopped, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Paused</em> state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>prepare </p></td>
+ * <td>{Initialized, Stopped} </p></td>
+ * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Prepared</em> state. Calling this method in an
+ * invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>prepareAsync </p></td>
+ * <td>{Initialized, Stopped} </p></td>
+ * <td>{Idle, Prepared, Started, Paused, PlaybackCompleted, Error} </p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Preparing</em> state. Calling this method in an
+ * invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>release </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>After {@link #close()}, the object is no longer available. </p></td></tr>
+ * <tr><td>reset </p></td>
+ * <td>{Idle, Initialized, Prepared, Started, Paused, Stopped,
+ * PlaybackCompleted, Error}</p></td>
+ * <td>{}</p></td>
+ * <td>After {@link #reset()}, the object is like being just created.</p></td></tr>
+ * <tr><td>seekTo </p></td>
+ * <td>{Prepared, Started, Paused, PlaybackCompleted} </p></td>
+ * <td>{Idle, Initialized, Stopped, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an invalid state transfers the
+ * object to the <em>Error</em> state. </p></td></tr>
+ * <tr><td>setAudioAttributes </p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method does not change the state. In order for the
+ * target audio attributes type to become effective, this method must be called before
+ * prepare() or prepareAsync().</p></td></tr>
+ * <tr><td>setAudioSessionId </p></td>
+ * <td>{Idle} </p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
+ * Error} </p></td>
+ * <td>This method must be called in idle state as the audio session ID must be known before
+ * calling setDataSource or setPlaylist. Calling it does not change the object
+ * state. </p></td></tr>
+ * <tr><td>setAudioStreamType (deprecated)</p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method does not change the state. In order for the
+ * target audio stream type to become effective, this method must be called before
+ * prepare() or prepareAsync().</p></td></tr>
+ * <tr><td>setAuxEffectSendLevel </p></td>
+ * <td>any</p></td>
+ * <td>{} </p></td>
+ * <td>Calling this method does not change the object state. </p></td></tr>
+ * <tr><td>setDataSource </p></td>
+ * <td>{Idle} </p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
+ * Error} </p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Initialized</em> state. Calling this method in an
+ * invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>setPlaylist </p></td>
+ * <td>{Idle} </p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted,
+ * Error} </p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Initialized</em> state. Calling this method in an
+ * invalid state throws an IllegalStateException.</p></td></tr>
+ * <tr><td>setDisplay </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setSurface </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setVideoScalingMode </p></td>
+ * <td>{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} </p></td>
+ * <td>{Idle, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ * <tr><td>setLooping </p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method in a valid state does not change
+ * the state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>isLooping </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>registerDrmEventCallback </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>registerEventCallback </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setPlaybackParams</p></td>
+ * <td>{Initialized, Prepared, Started, Paused, PlaybackCompleted, Error}</p></td>
+ * <td>{Idle, Stopped} </p></td>
+ * <td>This method will change state in some cases, depending on when it's called.
+ * </p></td></tr>
+ * <tr><td>setScreenOnWhilePlaying</></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state. </p></td></tr>
+ * <tr><td>setVolume </p></td>
+ * <td>{Idle, Initialized, Stopped, Prepared, Started, Paused,
+ * PlaybackCompleted}</p></td>
+ * <td>{Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.
+ * <tr><td>setWakeMode </p></td>
+ * <td>any </p></td>
+ * <td>{} </p></td>
+ * <td>This method can be called in any state and calling it does not change
+ * the object state.</p></td></tr>
+ * <tr><td>start </p></td>
+ * <td>{Prepared, Started, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Stopped, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Started</em> state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>stop </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method in a valid state transfers the
+ * object to the <em>Stopped</em> state. Calling this method in an
+ * invalid state transfers the object to the <em>Error</em> state.</p></td></tr>
+ * <tr><td>getTrackInfo </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ * <tr><td>addTimedTextSource </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ * <tr><td>selectTrack </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ * <tr><td>deselectTrack </p></td>
+ * <td>{Prepared, Started, Stopped, Paused, PlaybackCompleted}</p></td>
+ * <td>{Idle, Initialized, Error}</p></td>
+ * <td>Successful invoke of this method does not change the state.</p></td></tr>
+ *
+ * </table>
+ *
+ * <a name="Permissions"></a>
+ * <h3>Permissions</h3>
+ * <p>One may need to declare a corresponding WAKE_LOCK permission {@link
+ * android.R.styleable#AndroidManifestUsesPermission <uses-permission>}
+ * element.
+ *
+ * <p>This class requires the {@link android.Manifest.permission#INTERNET} permission
+ * when used with network-based content.
+ *
+ * <a name="Callbacks"></a>
+ * <h3>Callbacks</h3>
+ * <p>Applications may want to register for informational and error
+ * events in order to be informed of some internal state update and
+ * possible runtime errors during playback or streaming. Registration for
+ * these events is done by properly setting the appropriate listeners (via calls
+ * to
+ * {@link #registerEventCallback(Executor, EventCallback)},
+ * {@link #registerDrmEventCallback(Executor, DrmEventCallback)}).
+ * In order to receive the respective callback
+ * associated with these listeners, applications are required to create
+ * MediaPlayer2 objects on a thread with its own Looper running (main UI
+ * thread by default has a Looper running).
+ *
+ * @hide
+ */
+public final class MediaPlayer2Impl extends MediaPlayer2 {
+ static {
+ System.loadLibrary("media2_jni");
+ native_init();
+ }
+
+ private final static String TAG = "MediaPlayer2Impl";
+
+ private long mNativeContext; // accessed by native methods
+ private long mNativeSurfaceTexture; // accessed by native methods
+ private int mListenerContext; // accessed by native methods
+ private SurfaceHolder mSurfaceHolder;
+ private EventHandler mEventHandler;
+ private PowerManager.WakeLock mWakeLock = null;
+ private boolean mScreenOnWhilePlaying;
+ private boolean mStayAwake;
+ private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
+ private int mUsage = -1;
+ private boolean mBypassInterruptionPolicy;
+ private final CloseGuard mGuard = CloseGuard.get();
+
+ private List<DataSourceDesc> mPlaylist;
+ private int mPLCurrentIndex = 0;
+ private int mPLNextIndex = -1;
+ private int mLoopingMode = LOOPING_MODE_NONE;
+
+ // Modular DRM
+ private UUID mDrmUUID;
+ private final Object mDrmLock = new Object();
+ private DrmInfoImpl mDrmInfoImpl;
+ private MediaDrm mDrmObj;
+ private byte[] mDrmSessionId;
+ private boolean mDrmInfoResolved;
+ private boolean mActiveDrmScheme;
+ private boolean mDrmConfigAllowed;
+ private boolean mDrmProvisioningInProgress;
+ private boolean mPrepareDrmInProgress;
+ private ProvisioningThread mDrmProvisioningThread;
+
+ /**
+ * Default constructor.
+ * <p>When done with the MediaPlayer2Impl, you should call {@link #close()},
+ * to free the resources. If not released, too many MediaPlayer2Impl instances may
+ * result in an exception.</p>
+ */
+ public MediaPlayer2Impl() {
+ Looper looper;
+ if ((looper = Looper.myLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else if ((looper = Looper.getMainLooper()) != null) {
+ mEventHandler = new EventHandler(this, looper);
+ } else {
+ mEventHandler = null;
+ }
+
+ mTimeProvider = new TimeProvider(this);
+ mOpenSubtitleSources = new Vector<InputStream>();
+ mGuard.open("close");
+
+ /* Native setup requires a weak reference to our object.
+ * It's easier to create it here than in C++.
+ */
+ native_setup(new WeakReference<MediaPlayer2Impl>(this));
+ }
+
+ /*
+ * Update the MediaPlayer2Impl SurfaceTexture.
+ * Call after setting a new display surface.
+ */
+ private native void _setVideoSurface(Surface surface);
+
+ /* Do not change these values (starting with INVOKE_ID) without updating
+ * their counterparts in include/media/mediaplayer2.h!
+ */
+ private static final int INVOKE_ID_GET_TRACK_INFO = 1;
+ private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE = 2;
+ private static final int INVOKE_ID_ADD_EXTERNAL_SOURCE_FD = 3;
+ private static final int INVOKE_ID_SELECT_TRACK = 4;
+ private static final int INVOKE_ID_DESELECT_TRACK = 5;
+ private static final int INVOKE_ID_SET_VIDEO_SCALE_MODE = 6;
+ private static final int INVOKE_ID_GET_SELECTED_TRACK = 7;
+
+ /**
+ * Create a request parcel which can be routed to the native media
+ * player using {@link #invoke(Parcel, Parcel)}. The Parcel
+ * returned has the proper InterfaceToken set. The caller should
+ * not overwrite that token, i.e it can only append data to the
+ * Parcel.
+ *
+ * @return A parcel suitable to hold a request for the native
+ * player.
+ * {@hide}
+ */
+ @Override
+ public Parcel newRequest() {
+ Parcel parcel = Parcel.obtain();
+ return parcel;
+ }
+
+ /**
+ * Invoke a generic method on the native player using opaque
+ * parcels for the request and reply. Both payloads' format is a
+ * convention between the java caller and the native player.
+ * Must be called after setDataSource or setPlaylist to make sure a native player
+ * exists. On failure, a RuntimeException is thrown.
+ *
+ * @param request Parcel with the data for the extension. The
+ * caller must use {@link #newRequest()} to get one.
+ *
+ * @param reply Output parcel with the data returned by the
+ * native player.
+ * {@hide}
+ */
+ @Override
+ public void invoke(Parcel request, Parcel reply) {
+ int retcode = native_invoke(request, reply);
+ reply.setDataPosition(0);
+ if (retcode != 0) {
+ throw new RuntimeException("failure code: " + retcode);
+ }
+ }
+
+ /**
+ * Sets the {@link SurfaceHolder} to use for displaying the video
+ * portion of the media.
+ *
+ * Either a surface holder or surface must be set if a display or video sink
+ * is needed. Not calling this method or {@link #setSurface(Surface)}
+ * when playing back a video will result in only the audio track being played.
+ * A null surface holder or surface will result in only the audio track being
+ * played.
+ *
+ * @param sh the SurfaceHolder to use for video display
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released.
+ * @hide
+ */
+ @Override
+ public void setDisplay(SurfaceHolder sh) {
+ mSurfaceHolder = sh;
+ Surface surface;
+ if (sh != null) {
+ surface = sh.getSurface();
+ } else {
+ surface = null;
+ }
+ _setVideoSurface(surface);
+ updateSurfaceScreenOn();
+ }
+
+ /**
+ * Sets the {@link Surface} to be used as the sink for the video portion of
+ * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but
+ * does not support {@link #setScreenOnWhilePlaying(boolean)}. Setting a
+ * Surface will un-set any Surface or SurfaceHolder that was previously set.
+ * A null surface will result in only the audio track being played.
+ *
+ * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps
+ * returned from {@link SurfaceTexture#getTimestamp()} will have an
+ * unspecified zero point. These timestamps cannot be directly compared
+ * between different media sources, different instances of the same media
+ * source, or multiple runs of the same program. The timestamp is normally
+ * monotonically increasing and is unaffected by time-of-day adjustments,
+ * but it is reset when the position is set.
+ *
+ * @param surface The {@link Surface} to be used for the video portion of
+ * the media.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released.
+ */
+ @Override
+ public void setSurface(Surface surface) {
+ if (mScreenOnWhilePlaying && surface != null) {
+ Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective for Surface");
+ }
+ mSurfaceHolder = null;
+ _setVideoSurface(surface);
+ updateSurfaceScreenOn();
+ }
+
+ /**
+ * Sets video scaling mode. To make the target video scaling mode
+ * effective during playback, this method must be called after
+ * data source is set. If not called, the default video
+ * scaling mode is {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT}.
+ *
+ * <p> The supported video scaling modes are:
+ * <ul>
+ * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT}
+ * <li> {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}
+ * </ul>
+ *
+ * @param mode target video scaling mode. Must be one of the supported
+ * video scaling modes; otherwise, IllegalArgumentException will be thrown.
+ *
+ * @see MediaPlayer2#VIDEO_SCALING_MODE_SCALE_TO_FIT
+ * @see MediaPlayer2#VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
+ * @hide
+ */
+ @Override
+ public void setVideoScalingMode(int mode) {
+ if (!isVideoScalingModeSupported(mode)) {
+ final String msg = "Scaling mode " + mode + " is not supported";
+ throw new IllegalArgumentException(msg);
+ }
+ Parcel request = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ request.writeInt(INVOKE_ID_SET_VIDEO_SCALE_MODE);
+ request.writeInt(mode);
+ invoke(request, reply);
+ } finally {
+ request.recycle();
+ reply.recycle();
+ }
+ }
+
+ /**
+ * Discards all pending commands.
+ */
+ @Override
+ public void clearPendingCommands() {
+ }
+
+ /**
+ * Sets the data source as described by a DataSourceDesc.
+ *
+ * @param dsd the descriptor of data source you want to play
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws NullPointerException if dsd is null
+ */
+ @Override
+ public void setDataSource(@NonNull DataSourceDesc dsd) throws IOException {
+ Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
+ mPlaylist = Collections.synchronizedList(new ArrayList<DataSourceDesc>(1));
+ mPlaylist.add(dsd);
+ mPLCurrentIndex = 0;
+ setDataSourcePriv(dsd);
+ }
+
+ /**
+ * Gets the current data source as described by a DataSourceDesc.
+ *
+ * @return the current DataSourceDesc
+ */
+ @Override
+ public DataSourceDesc getCurrentDataSource() {
+ if (mPlaylist == null) {
+ return null;
+ }
+ return mPlaylist.get(mPLCurrentIndex);
+ }
+
+ /**
+ * Sets the play list.
+ *
+ * If startIndex falls outside play list range, it will be clamped to the nearest index
+ * in the play list.
+ *
+ * @param pl the play list of data source you want to play
+ * @param startIndex the index of the DataSourceDesc in the play list you want to play first
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws IllegalArgumentException if pl is null or empty, or pl contains null DataSourceDesc
+ */
+ @Override
+ public void setPlaylist(@NonNull List<DataSourceDesc> pl, int startIndex)
+ throws IOException {
+ if (pl == null || pl.size() == 0) {
+ throw new IllegalArgumentException("play list cannot be null or empty.");
+ }
+ HashSet ids = new HashSet(pl.size());
+ for (DataSourceDesc dsd : pl) {
+ if (dsd == null) {
+ throw new IllegalArgumentException("DataSourceDesc in play list cannot be null.");
+ }
+ if (ids.add(dsd.getId()) == false) {
+ throw new IllegalArgumentException("DataSourceDesc Id in play list should be unique.");
+ }
+ }
+
+ if (startIndex < 0) {
+ startIndex = 0;
+ } else if (startIndex >= pl.size()) {
+ startIndex = pl.size() - 1;
+ }
+
+ mPlaylist = Collections.synchronizedList(new ArrayList(pl));
+ mPLCurrentIndex = startIndex;
+ setDataSourcePriv(mPlaylist.get(startIndex));
+ // TODO: handle the preparation of next source in the play list.
+ // It should be processed after current source is prepared.
+ }
+
+ /**
+ * Gets a copy of the play list.
+ *
+ * @return a copy of the play list used by {@link MediaPlayer2}
+ */
+ @Override
+ public List<DataSourceDesc> getPlaylist() {
+ if (mPlaylist == null) {
+ return null;
+ }
+ return new ArrayList(mPlaylist);
+ }
+
+ /**
+ * Sets the index of current DataSourceDesc in the play list to be played.
+ *
+ * @param index the index of DataSourceDesc in the play list you want to play
+ * @throws IllegalArgumentException if the play list is null
+ * @throws NullPointerException if index is outside play list range
+ */
+ @Override
+ public void setCurrentPlaylistItem(int index) {
+ if (mPlaylist == null) {
+ throw new IllegalArgumentException("play list has not been set yet.");
+ }
+ if (index < 0 || index >= mPlaylist.size()) {
+ throw new IndexOutOfBoundsException("index is out of play list range.");
+ }
+
+ if (index == mPLCurrentIndex) {
+ return;
+ }
+
+ // TODO: in playing state, stop current source and start to play source of index.
+ mPLCurrentIndex = index;
+ }
+
+ /**
+ * Sets the index of next-to-be-played DataSourceDesc in the play list.
+ *
+ * @param index the index of next-to-be-played DataSourceDesc in the play list
+ * @throws IllegalArgumentException if the play list is null
+ * @throws NullPointerException if index is outside play list range
+ */
+ @Override
+ public void setNextPlaylistItem(int index) {
+ if (mPlaylist == null) {
+ throw new IllegalArgumentException("play list has not been set yet.");
+ }
+ if (index < 0 || index >= mPlaylist.size()) {
+ throw new IndexOutOfBoundsException("index is out of play list range.");
+ }
+
+ if (index == mPLNextIndex) {
+ return;
+ }
+
+ // TODO: prepare the new next-to-be-played DataSourceDesc
+ mPLNextIndex = index;
+ }
+
+ /**
+ * Gets the current index of play list.
+ *
+ * @return the index of the current DataSourceDesc in the play list
+ */
+ @Override
+ public int getCurrentPlaylistItemIndex() {
+ return mPLCurrentIndex;
+ }
+
+ /**
+ * Sets the looping mode of the play list.
+ * The mode shall be one of {@link #LOOPING_MODE_NONE}, {@link #LOOPING_MODE_FULL},
+ * {@link #LOOPING_MODE_SINGLE}, {@link #LOOPING_MODE_SHUFFLE}.
+ *
+ * @param mode the mode in which the play list will be played
+ * @throws IllegalArgumentException if mode is not supported
+ */
+ @Override
+ public void setLoopingMode(@LoopingMode int mode) {
+ if (mode != LOOPING_MODE_NONE
+ && mode != LOOPING_MODE_FULL
+ && mode != LOOPING_MODE_SINGLE
+ && mode != LOOPING_MODE_SHUFFLE) {
+ throw new IllegalArgumentException("mode is not supported.");
+ }
+ mLoopingMode = mode;
+ if (mPlaylist == null) {
+ return;
+ }
+
+ // TODO: handle the new mode if necessary.
+ }
+
+ /**
+ * Gets the looping mode of play list.
+ *
+ * @return the looping mode of the play list
+ */
+ @Override
+ public int getLoopingMode() {
+ return mPLCurrentIndex;
+ }
+
+ /**
+ * Moves the DataSourceDesc at indexFrom in the play list to indexTo.
+ *
+ * @throws IllegalArgumentException if the play list is null
+ * @throws IndexOutOfBoundsException if indexFrom or indexTo is outside play list range
+ */
+ @Override
+ public void movePlaylistItem(int indexFrom, int indexTo) {
+ if (mPlaylist == null) {
+ throw new IllegalArgumentException("play list has not been set yet.");
+ }
+ // TODO: move the DataSourceDesc from indexFrom to indexTo.
+ }
+
+ /**
+ * Removes the DataSourceDesc at index in the play list.
+ *
+ * If index is same as the current index of the play list, current DataSourceDesc
+ * will be stopped and playback moves to next source in the list.
+ *
+ * @return the removed DataSourceDesc at index in the play list
+ * @throws IllegalArgumentException if the play list is null
+ * @throws IndexOutOfBoundsException if index is outside play list range
+ */
+ @Override
+ public DataSourceDesc removePlaylistItem(int index) {
+ if (mPlaylist == null) {
+ throw new IllegalArgumentException("play list has not been set yet.");
+ }
+
+ DataSourceDesc oldDsd = mPlaylist.remove(index);
+ // TODO: if index == mPLCurrentIndex, stop current source and move to next one.
+ // if index == mPLNextIndex, prepare the new next-to-be-played source.
+ return oldDsd;
+ }
+
+ /**
+ * Inserts the DataSourceDesc to the play list at position index.
+ *
+ * This will not change the DataSourceDesc currently being played.
+ * If index is less than or equal to the current index of the play list,
+ * the current index of the play list will be incremented correspondingly.
+ *
+ * @param index the index you want to add dsd to the play list
+ * @param dsd the descriptor of data source you want to add to the play list
+ * @throws IndexOutOfBoundsException if index is outside play list range
+ * @throws NullPointerException if dsd is null
+ */
+ @Override
+ public void addPlaylistItem(int index, DataSourceDesc dsd) {
+ Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
+
+ if (mPlaylist == null) {
+ if (index == 0) {
+ mPlaylist = Collections.synchronizedList(new ArrayList<DataSourceDesc>());
+ mPlaylist.add(dsd);
+ mPLCurrentIndex = 0;
+ return;
+ }
+ throw new IllegalArgumentException("index should be 0 for first DataSourceDesc.");
+ }
+
+ long id = dsd.getId();
+ for (DataSourceDesc pldsd : mPlaylist) {
+ if (id == pldsd.getId()) {
+ throw new IllegalArgumentException("Id of dsd already exists in the play list.");
+ }
+ }
+
+ mPlaylist.add(index, dsd);
+ if (index <= mPLCurrentIndex) {
+ ++mPLCurrentIndex;
+ }
+ }
+
+ /**
+ * replaces the DataSourceDesc at index in the play list with given dsd.
+ *
+ * When index is same as the current index of the play list, the current source
+ * will be stopped and the new source will be played, except that if new
+ * and old source only differ on end position and current media position is
+ * smaller then the new end position.
+ *
+ * This will not change the DataSourceDesc currently being played.
+ * If index is less than or equal to the current index of the play list,
+ * the current index of the play list will be incremented correspondingly.
+ *
+ * @param index the index you want to add dsd to the play list
+ * @param dsd the descriptor of data source you want to add to the play list
+ * @throws IndexOutOfBoundsException if index is outside play list range
+ * @throws NullPointerException if dsd is null
+ */
+ @Override
+ public DataSourceDesc editPlaylistItem(int index, DataSourceDesc dsd) {
+ Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
+ Preconditions.checkNotNull(mPlaylist, "the play list cannot be null");
+
+ long id = dsd.getId();
+ for (int i = 0; i < mPlaylist.size(); ++i) {
+ if (i == index) {
+ continue;
+ }
+ if (id == mPlaylist.get(i).getId()) {
+ throw new IllegalArgumentException("Id of dsd already exists in the play list.");
+ }
+ }
+
+ // TODO: if needed, stop playback of current source, and start new dsd.
+ DataSourceDesc oldDsd = mPlaylist.set(index, dsd);
+ return mPlaylist.set(index, dsd);
+ }
+
+ private void setDataSourcePriv(@NonNull DataSourceDesc dsd) throws IOException {
+ Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
+
+ switch (dsd.getType()) {
+ case DataSourceDesc.TYPE_CALLBACK:
+ setDataSourcePriv(dsd.getId(),
+ dsd.getMedia2DataSource());
+ break;
+
+ case DataSourceDesc.TYPE_FD:
+ setDataSourcePriv(dsd.getId(),
+ dsd.getFileDescriptor(),
+ dsd.getFileDescriptorOffset(),
+ dsd.getFileDescriptorLength());
+ break;
+
+ case DataSourceDesc.TYPE_URI:
+ setDataSourcePriv(dsd.getId(),
+ dsd.getUriContext(),
+ dsd.getUri(),
+ dsd.getUriHeaders(),
+ dsd.getUriCookies());
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /**
+ * To provide cookies for the subsequent HTTP requests, you can install your own default cookie
+ * handler and use other variants of setDataSource APIs instead. Alternatively, you can use
+ * this API to pass the cookies as a list of HttpCookie. If the app has not installed
+ * a CookieHandler already, this API creates a CookieManager and populates its CookieStore with
+ * the provided cookies. If the app has installed its own handler already, this API requires the
+ * handler to be of CookieManager type such that the API can update the manager’s CookieStore.
+ *
+ * <p><strong>Note</strong> that the cross domain redirection is allowed by default,
+ * but that can be changed with key/value pairs through the headers parameter with
+ * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value to
+ * disallow or allow cross domain redirection.
+ *
+ * @throws IllegalArgumentException if cookies are provided and the installed handler is not
+ * a CookieManager
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws NullPointerException if context or uri is null
+ * @throws IOException if uri has a file scheme and an I/O error occurs
+ */
+ private void setDataSourcePriv(long srcId, @NonNull Context context, @NonNull Uri uri,
+ @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies)
+ throws IOException {
+ if (context == null) {
+ throw new NullPointerException("context param can not be null.");
+ }
+
+ if (uri == null) {
+ throw new NullPointerException("uri param can not be null.");
+ }
+
+ if (cookies != null) {
+ CookieHandler cookieHandler = CookieHandler.getDefault();
+ if (cookieHandler != null && !(cookieHandler instanceof CookieManager)) {
+ throw new IllegalArgumentException("The cookie handler has to be of CookieManager "
+ + "type when cookies are provided.");
+ }
+ }
+
+ // The context and URI usually belong to the calling user. Get a resolver for that user
+ // and strip out the userId from the URI if present.
+ final ContentResolver resolver = context.getContentResolver();
+ final String scheme = uri.getScheme();
+ final String authority = ContentProvider.getAuthorityWithoutUserId(uri.getAuthority());
+ if (ContentResolver.SCHEME_FILE.equals(scheme)) {
+ setDataSourcePriv(srcId, uri.getPath(), null, null);
+ return;
+ } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
+ && Settings.AUTHORITY.equals(authority)) {
+ // Try cached ringtone first since the actual provider may not be
+ // encryption aware, or it may be stored on CE media storage
+ final int type = RingtoneManager.getDefaultType(uri);
+ final Uri cacheUri = RingtoneManager.getCacheForType(type, context.getUserId());
+ final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
+ if (attemptDataSource(srcId, resolver, cacheUri)) {
+ return;
+ } else if (attemptDataSource(srcId, resolver, actualUri)) {
+ return;
+ } else {
+ setDataSourcePriv(srcId, uri.toString(), headers, cookies);
+ }
+ } else {
+ // Try requested Uri locally first, or fallback to media server
+ if (attemptDataSource(srcId, resolver, uri)) {
+ return;
+ } else {
+ setDataSourcePriv(srcId, uri.toString(), headers, cookies);
+ }
+ }
+ }
+
+ private boolean attemptDataSource(long srcId, ContentResolver resolver, Uri uri) {
+ try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) {
+ if (afd.getDeclaredLength() < 0) {
+ setDataSourcePriv(srcId, afd.getFileDescriptor(), 0, DataSourceDesc.LONG_MAX);
+ } else {
+ setDataSourcePriv(srcId,
+ afd.getFileDescriptor(),
+ afd.getStartOffset(),
+ afd.getDeclaredLength());
+ }
+ return true;
+ } catch (NullPointerException | SecurityException | IOException ex) {
+ Log.w(TAG, "Couldn't open " + uri + ": " + ex);
+ return false;
+ }
+ }
+
+ private void setDataSourcePriv(
+ long srcId, String path, Map<String, String> headers, List<HttpCookie> cookies)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException
+ {
+ String[] keys = null;
+ String[] values = null;
+
+ if (headers != null) {
+ keys = new String[headers.size()];
+ values = new String[headers.size()];
+
+ int i = 0;
+ for (Map.Entry<String, String> entry: headers.entrySet()) {
+ keys[i] = entry.getKey();
+ values[i] = entry.getValue();
+ ++i;
+ }
+ }
+ setDataSourcePriv(srcId, path, keys, values, cookies);
+ }
+
+ private void setDataSourcePriv(long srcId, String path, String[] keys, String[] values,
+ List<HttpCookie> cookies)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+ final Uri uri = Uri.parse(path);
+ final String scheme = uri.getScheme();
+ if ("file".equals(scheme)) {
+ path = uri.getPath();
+ } else if (scheme != null) {
+ // handle non-file sources
+ nativeSetDataSource(
+ Media2HTTPService.createHTTPService(path, cookies),
+ path,
+ keys,
+ values);
+ return;
+ }
+
+ final File file = new File(path);
+ if (file.exists()) {
+ FileInputStream is = new FileInputStream(file);
+ FileDescriptor fd = is.getFD();
+ setDataSourcePriv(srcId, fd, 0, DataSourceDesc.LONG_MAX);
+ is.close();
+ } else {
+ throw new IOException("setDataSourcePriv failed.");
+ }
+ }
+
+ private native void nativeSetDataSource(
+ Media2HTTPService httpService, String path, String[] keys, String[] values)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
+
+ /**
+ * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
+ * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
+ * to close the file descriptor. It is safe to do so as soon as this call returns.
+ *
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws IllegalArgumentException if fd is not a valid FileDescriptor
+ * @throws IOException if fd can not be read
+ */
+ private void setDataSourcePriv(long srcId, FileDescriptor fd, long offset, long length)
+ throws IOException {
+ _setDataSource(fd, offset, length);
+ }
+
+ private native void _setDataSource(FileDescriptor fd, long offset, long length)
+ throws IOException;
+
+ /**
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws IllegalArgumentException if dataSource is not a valid Media2DataSource
+ */
+ private void setDataSourcePriv(long srcId, Media2DataSource dataSource) {
+ _setDataSource(dataSource);
+ }
+
+ private native void _setDataSource(Media2DataSource dataSource);
+
+ /**
+ * Prepares the player for playback, synchronously.
+ *
+ * After setting the datasource and the display surface, you need to either
+ * call prepare() or prepareAsync(). For files, it is OK to call prepare(),
+ * which blocks until MediaPlayer2 is ready for playback.
+ *
+ * @throws IOException if source can not be accessed
+ * @throws IllegalStateException if it is called in an invalid state
+ * @hide
+ */
+ @Override
+ public void prepare() throws IOException {
+ _prepare();
+ scanInternalSubtitleTracks();
+
+ // DrmInfo, if any, has been resolved by now.
+ synchronized (mDrmLock) {
+ mDrmInfoResolved = true;
+ }
+ }
+
+ private native void _prepare() throws IOException, IllegalStateException;
+
+ /**
+ * Prepares the player for playback, asynchronously.
+ *
+ * After setting the datasource and the display surface, you need to either
+ * call prepare() or prepareAsync(). For streams, you should call prepareAsync(),
+ * which returns immediately, rather than blocking until enough data has been
+ * buffered.
+ *
+ * @throws IllegalStateException if it is called in an invalid state
+ */
+ @Override
+ public native void prepareAsync();
+
+ /**
+ * Starts or resumes playback. If playback had previously been paused,
+ * playback will continue from where it was paused. If playback had
+ * been stopped, or never started before, playback will start at the
+ * beginning.
+ *
+ * @throws IllegalStateException if it is called in an invalid state
+ */
+ @Override
+ public void play() {
+ stayAwake(true);
+ _start();
+ }
+
+ private native void _start() throws IllegalStateException;
+
+
+ private int getAudioStreamType() {
+ if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+ mStreamType = _getAudioStreamType();
+ }
+ return mStreamType;
+ }
+
+ private native int _getAudioStreamType() throws IllegalStateException;
+
+ /**
+ * Stops playback after playback has been started or paused.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ * #hide
+ */
+ @Override
+ public void stop() {
+ stayAwake(false);
+ _stop();
+ }
+
+ private native void _stop() throws IllegalStateException;
+
+ /**
+ * Pauses playback. Call play() to resume.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ */
+ @Override
+ public void pause() {
+ stayAwake(false);
+ _pause();
+ }
+
+ private native void _pause() throws IllegalStateException;
+
+ //--------------------------------------------------------------------------
+ // Explicit Routing
+ //--------------------
+ private AudioDeviceInfo mPreferredDevice = null;
+
+ /**
+ * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
+ * the output from this MediaPlayer2.
+ * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio sink or source.
+ * If deviceInfo is null, default routing is restored.
+ * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
+ * does not correspond to a valid audio device.
+ */
+ @Override
+ public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
+ if (deviceInfo != null && !deviceInfo.isSink()) {
+ return false;
+ }
+ int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
+ boolean status = native_setOutputDevice(preferredDeviceId);
+ if (status == true) {
+ synchronized (this) {
+ mPreferredDevice = deviceInfo;
+ }
+ }
+ return status;
+ }
+
+ /**
+ * Returns the selected output specified by {@link #setPreferredDevice}. Note that this
+ * is not guaranteed to correspond to the actual device being used for playback.
+ */
+ @Override
+ public AudioDeviceInfo getPreferredDevice() {
+ synchronized (this) {
+ return mPreferredDevice;
+ }
+ }
+
+ /**
+ * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaPlayer2
+ * Note: The query is only valid if the MediaPlayer2 is currently playing.
+ * If the player is not playing, the returned device can be null or correspond to previously
+ * selected device when the player was last active.
+ */
+ @Override
+ public AudioDeviceInfo getRoutedDevice() {
+ int deviceId = native_getRoutedDeviceId();
+ if (deviceId == 0) {
+ return null;
+ }
+ AudioDeviceInfo[] devices =
+ AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_OUTPUTS);
+ for (int i = 0; i < devices.length; i++) {
+ if (devices[i].getId() == deviceId) {
+ return devices[i];
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
+ */
+ private void enableNativeRoutingCallbacksLocked(boolean enabled) {
+ if (mRoutingChangeListeners.size() == 0) {
+ native_enableDeviceCallback(enabled);
+ }
+ }
+
+ /**
+ * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+ * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)}
+ * by an app to receive (re)routing notifications.
+ */
+ @GuardedBy("mRoutingChangeListeners")
+ private ArrayMap<AudioRouting.OnRoutingChangedListener,
+ NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();
+
+ /**
+ * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+ * changes on this MediaPlayer2.
+ * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+ * notifications of rerouting events.
+ * @param handler Specifies the {@link Handler} object for the thread on which to execute
+ * the callback. If <code>null</code>, the handler on the main looper will be used.
+ */
+ @Override
+ public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
+ Handler handler) {
+ synchronized (mRoutingChangeListeners) {
+ if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
+ enableNativeRoutingCallbacksLocked(true);
+ mRoutingChangeListeners.put(
+ listener, new NativeRoutingEventHandlerDelegate(this, listener,
+ handler != null ? handler : mEventHandler));
+ }
+ }
+ }
+
+ /**
+ * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+ * to receive rerouting notifications.
+ * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+ * to remove.
+ */
+ @Override
+ public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+ synchronized (mRoutingChangeListeners) {
+ if (mRoutingChangeListeners.containsKey(listener)) {
+ mRoutingChangeListeners.remove(listener);
+ enableNativeRoutingCallbacksLocked(false);
+ }
+ }
+ }
+
+ private native final boolean native_setOutputDevice(int deviceId);
+ private native final int native_getRoutedDeviceId();
+ private native final void native_enableDeviceCallback(boolean enabled);
+
+ /**
+ * Set the low-level power management behavior for this MediaPlayer2. This
+ * can be used when the MediaPlayer2 is not playing through a SurfaceHolder
+ * set with {@link #setDisplay(SurfaceHolder)} and thus can use the
+ * high-level {@link #setScreenOnWhilePlaying(boolean)} feature.
+ *
+ * <p>This function has the MediaPlayer2 access the low-level power manager
+ * service to control the device's power usage while playing is occurring.
+ * The parameter is a combination of {@link android.os.PowerManager} wake flags.
+ * Use of this method requires {@link android.Manifest.permission#WAKE_LOCK}
+ * permission.
+ * By default, no attempt is made to keep the device awake during playback.
+ *
+ * @param context the Context to use
+ * @param mode the power/wake mode to set
+ * @see android.os.PowerManager
+ * @hide
+ */
+ @Override
+ public void setWakeMode(Context context, int mode) {
+ boolean washeld = false;
+
+ /* Disable persistant wakelocks in media player based on property */
+ if (SystemProperties.getBoolean("audio.offload.ignore_setawake", false) == true) {
+ Log.w(TAG, "IGNORING setWakeMode " + mode);
+ return;
+ }
+
+ if (mWakeLock != null) {
+ if (mWakeLock.isHeld()) {
+ washeld = true;
+ mWakeLock.release();
+ }
+ mWakeLock = null;
+ }
+
+ PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(mode|PowerManager.ON_AFTER_RELEASE, MediaPlayer2Impl.class.getName());
+ mWakeLock.setReferenceCounted(false);
+ if (washeld) {
+ mWakeLock.acquire();
+ }
+ }
+
+ /**
+ * Control whether we should use the attached SurfaceHolder to keep the
+ * screen on while video playback is occurring. This is the preferred
+ * method over {@link #setWakeMode} where possible, since it doesn't
+ * require that the application have permission for low-level wake lock
+ * access.
+ *
+ * @param screenOn Supply true to keep the screen on, false to allow it
+ * to turn off.
+ * @hide
+ */
+ @Override
+ public void setScreenOnWhilePlaying(boolean screenOn) {
+ if (mScreenOnWhilePlaying != screenOn) {
+ if (screenOn && mSurfaceHolder == null) {
+ Log.w(TAG, "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder");
+ }
+ mScreenOnWhilePlaying = screenOn;
+ updateSurfaceScreenOn();
+ }
+ }
+
+ private void stayAwake(boolean awake) {
+ if (mWakeLock != null) {
+ if (awake && !mWakeLock.isHeld()) {
+ mWakeLock.acquire();
+ } else if (!awake && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+ mStayAwake = awake;
+ updateSurfaceScreenOn();
+ }
+
+ private void updateSurfaceScreenOn() {
+ if (mSurfaceHolder != null) {
+ mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
+ }
+ }
+
+ /**
+ * Returns the width of the video.
+ *
+ * @return the width of the video, or 0 if there is no video,
+ * no display surface was set, or the width has not been determined
+ * yet. The {@code EventCallback} can be registered via
+ * {@link #registerEventCallback(Executor, EventCallback)} to provide a
+ * notification {@code EventCallback.onVideoSizeChanged} when the width is available.
+ */
+ @Override
+ public native int getVideoWidth();
+
+ /**
+ * Returns the height of the video.
+ *
+ * @return the height of the video, or 0 if there is no video,
+ * no display surface was set, or the height has not been determined
+ * yet. The {@code EventCallback} can be registered via
+ * {@link #registerEventCallback(Executor, EventCallback)} to provide a
+ * notification {@code EventCallback.onVideoSizeChanged} when the height is available.
+ */
+ @Override
+ public native int getVideoHeight();
+
+ /**
+ * Return Metrics data about the current player.
+ *
+ * @return a {@link PersistableBundle} containing the set of attributes and values
+ * available for the media being handled by this instance of MediaPlayer2
+ * The attributes are descibed in {@link MetricsConstants}.
+ *
+ * Additional vendor-specific fields may also be present in
+ * the return value.
+ */
+ @Override
+ public PersistableBundle getMetrics() {
+ PersistableBundle bundle = native_getMetrics();
+ return bundle;
+ }
+
+ private native PersistableBundle native_getMetrics();
+
+ /**
+ * Checks whether the MediaPlayer2 is playing.
+ *
+ * @return true if currently playing, false otherwise
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released.
+ */
+ @Override
+ public native boolean isPlaying();
+
+ /**
+ * Gets the current buffering management params used by the source component.
+ * Calling it only after {@code setDataSource} has been called.
+ * Each type of data source might have different set of default params.
+ *
+ * @return the current buffering management params used by the source component.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized, or {@code setDataSource} has not been called.
+ * @hide
+ */
+ @Override
+ @NonNull
+ public native BufferingParams getBufferingParams();
+
+ /**
+ * Sets buffering management params.
+ * The object sets its internal BufferingParams to the input, except that the input is
+ * invalid or not supported.
+ * Call it only after {@code setDataSource} has been called.
+ * The input is a hint to MediaPlayer2.
+ *
+ * @param params the buffering management params.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released, or {@code setDataSource} has not been called.
+ * @throws IllegalArgumentException if params is invalid or not supported.
+ * @hide
+ */
+ @Override
+ public native void setBufferingParams(@NonNull BufferingParams params);
+
+ /**
+ * Sets playback rate and audio mode.
+ *
+ * @param rate the ratio between desired playback rate and normal one.
+ * @param audioMode audio playback mode. Must be one of the supported
+ * audio modes.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ * @throws IllegalArgumentException if audioMode is not supported.
+ *
+ * @hide
+ */
+ @Override
+ @NonNull
+ public PlaybackParams easyPlaybackParams(float rate, @PlaybackRateAudioMode int audioMode) {
+ PlaybackParams params = new PlaybackParams();
+ params.allowDefaults();
+ switch (audioMode) {
+ case PLAYBACK_RATE_AUDIO_MODE_DEFAULT:
+ params.setSpeed(rate).setPitch(1.0f);
+ break;
+ case PLAYBACK_RATE_AUDIO_MODE_STRETCH:
+ params.setSpeed(rate).setPitch(1.0f)
+ .setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_FAIL);
+ break;
+ case PLAYBACK_RATE_AUDIO_MODE_RESAMPLE:
+ params.setSpeed(rate).setPitch(rate);
+ break;
+ default:
+ final String msg = "Audio playback mode " + audioMode + " is not supported";
+ throw new IllegalArgumentException(msg);
+ }
+ return params;
+ }
+
+ /**
+ * Sets playback rate using {@link PlaybackParams}. The object sets its internal
+ * PlaybackParams to the input, except that the object remembers previous speed
+ * when input speed is zero. This allows the object to resume at previous speed
+ * when play() is called. Calling it before the object is prepared does not change
+ * the object state. After the object is prepared, calling it with zero speed is
+ * equivalent to calling pause(). After the object is prepared, calling it with
+ * non-zero speed is equivalent to calling play().
+ *
+ * @param params the playback params.
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized or has been released.
+ * @throws IllegalArgumentException if params is not supported.
+ */
+ @Override
+ public native void setPlaybackParams(@NonNull PlaybackParams params);
+
+ /**
+ * Gets the playback params, containing the current playback rate.
+ *
+ * @return the playback params.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ */
+ @Override
+ @NonNull
+ public native PlaybackParams getPlaybackParams();
+
+ /**
+ * Sets A/V sync mode.
+ *
+ * @param params the A/V sync params to apply
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ * @throws IllegalArgumentException if params are not supported.
+ */
+ @Override
+ public native void setSyncParams(@NonNull SyncParams params);
+
+ /**
+ * Gets the A/V sync mode.
+ *
+ * @return the A/V sync params
+ *
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized.
+ */
+ @Override
+ @NonNull
+ public native SyncParams getSyncParams();
+
+ private native final void _seekTo(long msec, int mode);
+
+ /**
+ * Moves the media to specified time position by considering the given mode.
+ * <p>
+ * When seekTo is finished, the user will be notified via OnSeekComplete supplied by the user.
+ * There is at most one active seekTo processed at any time. If there is a to-be-completed
+ * seekTo, new seekTo requests will be queued in such a way that only the last request
+ * is kept. When current seekTo is completed, the queued request will be processed if
+ * that request is different from just-finished seekTo operation, i.e., the requested
+ * position or mode is different.
+ *
+ * @param msec the offset in milliseconds from the start to seek to.
+ * When seeking to the given time position, there is no guarantee that the data source
+ * has a frame located at the position. When this happens, a frame nearby will be rendered.
+ * If msec is negative, time position zero will be used.
+ * If msec is larger than duration, duration will be used.
+ * @param mode the mode indicating where exactly to seek to.
+ * Use {@link #SEEK_PREVIOUS_SYNC} if one wants to seek to a sync frame
+ * that has a timestamp earlier than or the same as msec. Use
+ * {@link #SEEK_NEXT_SYNC} if one wants to seek to a sync frame
+ * that has a timestamp later than or the same as msec. Use
+ * {@link #SEEK_CLOSEST_SYNC} if one wants to seek to a sync frame
+ * that has a timestamp closest to or the same as msec. Use
+ * {@link #SEEK_CLOSEST} if one wants to seek to a frame that may
+ * or may not be a sync frame but is closest to or the same as msec.
+ * {@link #SEEK_CLOSEST} often has larger performance overhead compared
+ * to the other options if there is no sync frame located at msec.
+ * @throws IllegalStateException if the internal player engine has not been
+ * initialized
+ * @throws IllegalArgumentException if the mode is invalid.
+ */
+ @Override
+ public void seekTo(long msec, @SeekMode int mode) {
+ if (mode < SEEK_PREVIOUS_SYNC || mode > SEEK_CLOSEST) {
+ final String msg = "Illegal seek mode: " + mode;
+ throw new IllegalArgumentException(msg);
+ }
+ // TODO: pass long to native, instead of truncating here.
+ if (msec > Integer.MAX_VALUE) {
+ Log.w(TAG, "seekTo offset " + msec + " is too large, cap to " + Integer.MAX_VALUE);
+ msec = Integer.MAX_VALUE;
+ } else if (msec < Integer.MIN_VALUE) {
+ Log.w(TAG, "seekTo offset " + msec + " is too small, cap to " + Integer.MIN_VALUE);
+ msec = Integer.MIN_VALUE;
+ }
+ _seekTo(msec, mode);
+ }
+
+ /**
+ * Get current playback position as a {@link MediaTimestamp}.
+ * <p>
+ * The MediaTimestamp represents how the media time correlates to the system time in
+ * a linear fashion using an anchor and a clock rate. During regular playback, the media
+ * time moves fairly constantly (though the anchor frame may be rebased to a current
+ * system time, the linear correlation stays steady). Therefore, this method does not
+ * need to be called often.
+ * <p>
+ * To help users get current playback position, this method always anchors the timestamp
+ * to the current {@link System#nanoTime system time}, so
+ * {@link MediaTimestamp#getAnchorMediaTimeUs} can be used as current playback position.
+ *
+ * @return a MediaTimestamp object if a timestamp is available, or {@code null} if no timestamp
+ * is available, e.g. because the media player has not been initialized.
+ *
+ * @see MediaTimestamp
+ */
+ @Override
+ @Nullable
+ public MediaTimestamp getTimestamp()
+ {
+ try {
+ // TODO: get the timestamp from native side
+ return new MediaTimestamp(
+ getCurrentPosition() * 1000L,
+ System.nanoTime(),
+ isPlaying() ? getPlaybackParams().getSpeed() : 0.f);
+ } catch (IllegalStateException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the current playback position.
+ *
+ * @return the current position in milliseconds
+ */
+ @Override
+ public native int getCurrentPosition();
+
+ /**
+ * Gets the duration of the file.
+ *
+ * @return the duration in milliseconds, if no duration is available
+ * (for example, if streaming live content), -1 is returned.
+ */
+ @Override
+ public native int getDuration();
+
+ /**
+ * Gets the media metadata.
+ *
+ * @param update_only controls whether the full set of available
+ * metadata is returned or just the set that changed since the
+ * last call. See {@see #METADATA_UPDATE_ONLY} and {@see
+ * #METADATA_ALL}.
+ *
+ * @param apply_filter if true only metadata that matches the
+ * filter is returned. See {@see #APPLY_METADATA_FILTER} and {@see
+ * #BYPASS_METADATA_FILTER}.
+ *
+ * @return The metadata, possibly empty. null if an error occured.
+ // FIXME: unhide.
+ * {@hide}
+ */
+ @Override
+ public Metadata getMetadata(final boolean update_only,
+ final boolean apply_filter) {
+ Parcel reply = Parcel.obtain();
+ Metadata data = new Metadata();
+
+ if (!native_getMetadata(update_only, apply_filter, reply)) {
+ reply.recycle();
+ return null;
+ }
+
+ // Metadata takes over the parcel, don't recycle it unless
+ // there is an error.
+ if (!data.parse(reply)) {
+ reply.recycle();
+ return null;
+ }
+ return data;
+ }
+
+ /**
+ * Set a filter for the metadata update notification and update
+ * retrieval. The caller provides 2 set of metadata keys, allowed
+ * and blocked. The blocked set always takes precedence over the
+ * allowed one.
+ * Metadata.MATCH_ALL and Metadata.MATCH_NONE are 2 sets available as
+ * shorthands to allow/block all or no metadata.
+ *
+ * By default, there is no filter set.
+ *
+ * @param allow Is the set of metadata the client is interested
+ * in receiving new notifications for.
+ * @param block Is the set of metadata the client is not interested
+ * in receiving new notifications for.
+ * @return The call status code.
+ *
+ // FIXME: unhide.
+ * {@hide}
+ */
+ @Override
+ public int setMetadataFilter(Set<Integer> allow, Set<Integer> block) {
+ // Do our serialization manually instead of calling
+ // Parcel.writeArray since the sets are made of the same type
+ // we avoid paying the price of calling writeValue (used by
+ // writeArray) which burns an extra int per element to encode
+ // the type.
+ Parcel request = newRequest();
+
+ // The parcel starts already with an interface token. There
+ // are 2 filters. Each one starts with a 4bytes number to
+ // store the len followed by a number of int (4 bytes as well)
+ // representing the metadata type.
+ int capacity = request.dataSize() + 4 * (1 + allow.size() + 1 + block.size());
+
+ if (request.dataCapacity() < capacity) {
+ request.setDataCapacity(capacity);
+ }
+
+ request.writeInt(allow.size());
+ for(Integer t: allow) {
+ request.writeInt(t);
+ }
+ request.writeInt(block.size());
+ for(Integer t: block) {
+ request.writeInt(t);
+ }
+ return native_setMetadataFilter(request);
+ }
+
+ /**
+ * Set the MediaPlayer2 to start when this MediaPlayer2 finishes playback
+ * (i.e. reaches the end of the stream).
+ * The media framework will attempt to transition from this player to
+ * the next as seamlessly as possible. The next player can be set at
+ * any time before completion, but shall be after setDataSource has been
+ * called successfully. The next player must be prepared by the
+ * app, and the application should not call play() on it.
+ * The next MediaPlayer2 must be different from 'this'. An exception
+ * will be thrown if next == this.
+ * The application may call setNextMediaPlayer(null) to indicate no
+ * next player should be started at the end of playback.
+ * If the current player is looping, it will keep looping and the next
+ * player will not be started.
+ *
+ * @param next the player to start after this one completes playback.
+ *
+ * @hide
+ */
+ @Override
+ public native void setNextMediaPlayer(MediaPlayer2 next);
+
+ /**
+ * Resets the MediaPlayer2 to its uninitialized state. After calling
+ * this method, you will have to initialize it again by setting the
+ * data source and calling prepare().
+ */
+ @Override
+ public void reset() {
+ mSelectedSubtitleTrackIndex = -1;
+ synchronized(mOpenSubtitleSources) {
+ for (final InputStream is: mOpenSubtitleSources) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ mOpenSubtitleSources.clear();
+ }
+ if (mSubtitleController != null) {
+ mSubtitleController.reset();
+ }
+ if (mTimeProvider != null) {
+ mTimeProvider.close();
+ mTimeProvider = null;
+ }
+
+ stayAwake(false);
+ _reset();
+ // make sure none of the listeners get called anymore
+ if (mEventHandler != null) {
+ mEventHandler.removeCallbacksAndMessages(null);
+ }
+
+ synchronized (mIndexTrackPairs) {
+ mIndexTrackPairs.clear();
+ mInbandTrackIndices.clear();
+ };
+
+ resetDrmState();
+ }
+
+ private native void _reset();
+
+ /**
+ * Set up a timer for {@link #TimeProvider}. {@link #TimeProvider} will be
+ * notified when the presentation time reaches (becomes greater than or equal to)
+ * the value specified.
+ *
+ * @param mediaTimeUs presentation time to get timed event callback at
+ * @hide
+ */
+ @Override
+ public void notifyAt(long mediaTimeUs) {
+ _notifyAt(mediaTimeUs);
+ }
+
+ private native void _notifyAt(long mediaTimeUs);
+
+ // Keep KEY_PARAMETER_* in sync with include/media/mediaplayer2.h
+ private final static int KEY_PARAMETER_AUDIO_ATTRIBUTES = 1400;
+ /**
+ * Sets the parameter indicated by key.
+ * @param key key indicates the parameter to be set.
+ * @param value value of the parameter to be set.
+ * @return true if the parameter is set successfully, false otherwise
+ * {@hide}
+ */
+ private native boolean setParameter(int key, Parcel value);
+
+ /**
+ * Sets the audio attributes for this MediaPlayer2.
+ * See {@link AudioAttributes} for how to build and configure an instance of this class.
+ * You must call this method before {@link #prepare()} or {@link #prepareAsync()} in order
+ * for the audio attributes to become effective thereafter.
+ * @param attributes a non-null set of audio attributes
+ * @throws IllegalArgumentException if the attributes are null or invalid.
+ */
+ @Override
+ public void setAudioAttributes(AudioAttributes attributes) {
+ if (attributes == null) {
+ final String msg = "Cannot set AudioAttributes to null";
+ throw new IllegalArgumentException(msg);
+ }
+ mUsage = attributes.getUsage();
+ mBypassInterruptionPolicy = (attributes.getAllFlags()
+ & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0;
+ Parcel pattributes = Parcel.obtain();
+ attributes.writeToParcel(pattributes, AudioAttributes.FLATTEN_TAGS);
+ setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, pattributes);
+ pattributes.recycle();
+ }
+
+ /**
+ * Sets the player to be looping or non-looping.
+ *
+ * @param looping whether to loop or not
+ * @hide
+ */
+ @Override
+ public native void setLooping(boolean looping);
+
+ /**
+ * Checks whether the MediaPlayer2 is looping or non-looping.
+ *
+ * @return true if the MediaPlayer2 is currently looping, false otherwise
+ * @hide
+ */
+ @Override
+ public native boolean isLooping();
+
+ /**
+ * Sets the volume on this player.
+ * This API is recommended for balancing the output of audio streams
+ * within an application. Unless you are writing an application to
+ * control user settings, this API should be used in preference to
+ * {@link AudioManager#setStreamVolume(int, int, int)} which sets the volume of ALL streams of
+ * a particular type. Note that the passed volume values are raw scalars in range 0.0 to 1.0.
+ * UI controls should be scaled logarithmically.
+ *
+ * @param leftVolume left volume scalar
+ * @param rightVolume right volume scalar
+ */
+ /*
+ * FIXME: Merge this into javadoc comment above when setVolume(float) is not @hide.
+ * The single parameter form below is preferred if the channel volumes don't need
+ * to be set independently.
+ */
+ @Override
+ public void setVolume(float leftVolume, float rightVolume) {
+ _setVolume(leftVolume, rightVolume);
+ }
+
+ private native void _setVolume(float leftVolume, float rightVolume);
+
+ /**
+ * Similar, excepts sets volume of all channels to same value.
+ * @hide
+ */
+ @Override
+ public void setVolume(float volume) {
+ setVolume(volume, volume);
+ }
+
+ /**
+ * Sets the audio session ID.
+ *
+ * @param sessionId the audio session ID.
+ * The audio session ID is a system wide unique identifier for the audio stream played by
+ * this MediaPlayer2 instance.
+ * The primary use of the audio session ID is to associate audio effects to a particular
+ * instance of MediaPlayer2: if an audio session ID is provided when creating an audio effect,
+ * this effect will be applied only to the audio content of media players within the same
+ * audio session and not to the output mix.
+ * When created, a MediaPlayer2 instance automatically generates its own audio session ID.
+ * However, it is possible to force this player to be part of an already existing audio session
+ * by calling this method.
+ * This method must be called before one of the overloaded <code> setDataSource </code> methods.
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws IllegalArgumentException if the sessionId is invalid.
+ */
+ @Override
+ public native void setAudioSessionId(int sessionId);
+
+ /**
+ * Returns the audio session ID.
+ *
+ * @return the audio session ID. {@see #setAudioSessionId(int)}
+ * Note that the audio session ID is 0 only if a problem occured when the MediaPlayer2 was contructed.
+ */
+ @Override
+ public native int getAudioSessionId();
+
+ /**
+ * Attaches an auxiliary effect to the player. A typical auxiliary effect is a reverberation
+ * effect which can be applied on any sound source that directs a certain amount of its
+ * energy to this effect. This amount is defined by setAuxEffectSendLevel().
+ * See {@link #setAuxEffectSendLevel(float)}.
+ * <p>After creating an auxiliary effect (e.g.
+ * {@link android.media.audiofx.EnvironmentalReverb}), retrieve its ID with
+ * {@link android.media.audiofx.AudioEffect#getId()} and use it when calling this method
+ * to attach the player to the effect.
+ * <p>To detach the effect from the player, call this method with a null effect id.
+ * <p>This method must be called after one of the overloaded <code> setDataSource </code>
+ * methods.
+ * @param effectId system wide unique id of the effect to attach
+ */
+ @Override
+ public native void attachAuxEffect(int effectId);
+
+
+ /**
+ * Sets the send level of the player to the attached auxiliary effect.
+ * See {@link #attachAuxEffect(int)}. The level value range is 0 to 1.0.
+ * <p>By default the send level is 0, so even if an effect is attached to the player
+ * this method must be called for the effect to be applied.
+ * <p>Note that the passed level value is a raw scalar. UI controls should be scaled
+ * logarithmically: the gain applied by audio framework ranges from -72dB to 0dB,
+ * so an appropriate conversion from linear UI input x to level is:
+ * x == 0 -> level = 0
+ * 0 < x <= R -> level = 10^(72*(x-R)/20/R)
+ * @param level send level scalar
+ */
+ @Override
+ public void setAuxEffectSendLevel(float level) {
+ _setAuxEffectSendLevel(level);
+ }
+
+ private native void _setAuxEffectSendLevel(float level);
+
+ /*
+ * @param request Parcel destinated to the media player.
+ * @param reply[out] Parcel that will contain the reply.
+ * @return The status code.
+ */
+ private native final int native_invoke(Parcel request, Parcel reply);
+
+
+ /*
+ * @param update_only If true fetch only the set of metadata that have
+ * changed since the last invocation of getMetadata.
+ * The set is built using the unfiltered
+ * notifications the native player sent to the
+ * MediaPlayer2Manager during that period of
+ * time. If false, all the metadatas are considered.
+ * @param apply_filter If true, once the metadata set has been built based on
+ * the value update_only, the current filter is applied.
+ * @param reply[out] On return contains the serialized
+ * metadata. Valid only if the call was successful.
+ * @return The status code.
+ */
+ private native final boolean native_getMetadata(boolean update_only,
+ boolean apply_filter,
+ Parcel reply);
+
+ /*
+ * @param request Parcel with the 2 serialized lists of allowed
+ * metadata types followed by the one to be
+ * dropped. Each list starts with an integer
+ * indicating the number of metadata type elements.
+ * @return The status code.
+ */
+ private native final int native_setMetadataFilter(Parcel request);
+
+ private static native final void native_init();
+ private native final void native_setup(Object mediaplayer2_this);
+ private native final void native_finalize();
+
+ /**
+ * Class for MediaPlayer2 to return each audio/video/subtitle track's metadata.
+ *
+ * @see android.media.MediaPlayer2#getTrackInfo
+ */
+ public static final class TrackInfoImpl extends TrackInfo {
+ /**
+ * Gets the track type.
+ * @return TrackType which indicates if the track is video, audio, timed text.
+ */
+ @Override
+ public int getTrackType() {
+ return mTrackType;
+ }
+
+ /**
+ * Gets the language code of the track.
+ * @return a language code in either way of ISO-639-1 or ISO-639-2.
+ * When the language is unknown or could not be determined,
+ * ISO-639-2 language code, "und", is returned.
+ */
+ @Override
+ public String getLanguage() {
+ String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
+ return language == null ? "und" : language;
+ }
+
+ /**
+ * Gets the {@link MediaFormat} of the track. If the format is
+ * unknown or could not be determined, null is returned.
+ */
+ @Override
+ public MediaFormat getFormat() {
+ if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT
+ || mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
+ return mFormat;
+ }
+ return null;
+ }
+
+ final int mTrackType;
+ final MediaFormat mFormat;
+
+ TrackInfoImpl(Parcel in) {
+ mTrackType = in.readInt();
+ // TODO: parcel in the full MediaFormat; currently we are using createSubtitleFormat
+ // even for audio/video tracks, meaning we only set the mime and language.
+ String mime = in.readString();
+ String language = in.readString();
+ mFormat = MediaFormat.createSubtitleFormat(mime, language);
+
+ if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
+ mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt());
+ mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt());
+ mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt());
+ }
+ }
+
+ /** @hide */
+ TrackInfoImpl(int type, MediaFormat format) {
+ mTrackType = type;
+ mFormat = format;
+ }
+
+ /**
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link android.os.Parcelable#PARCELABLE_WRITE_RETURN_VALUE}.
+ */
+ /* package private */ void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTrackType);
+ dest.writeString(getLanguage());
+
+ if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
+ dest.writeString(mFormat.getString(MediaFormat.KEY_MIME));
+ dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT));
+ dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_DEFAULT));
+ dest.writeInt(mFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE));
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder out = new StringBuilder(128);
+ out.append(getClass().getName());
+ out.append('{');
+ switch (mTrackType) {
+ case MEDIA_TRACK_TYPE_VIDEO:
+ out.append("VIDEO");
+ break;
+ case MEDIA_TRACK_TYPE_AUDIO:
+ out.append("AUDIO");
+ break;
+ case MEDIA_TRACK_TYPE_TIMEDTEXT:
+ out.append("TIMEDTEXT");
+ break;
+ case MEDIA_TRACK_TYPE_SUBTITLE:
+ out.append("SUBTITLE");
+ break;
+ default:
+ out.append("UNKNOWN");
+ break;
+ }
+ out.append(", " + mFormat.toString());
+ out.append("}");
+ return out.toString();
+ }
+
+ /**
+ * Used to read a TrackInfoImpl from a Parcel.
+ */
+ /* package private */ static final Parcelable.Creator<TrackInfoImpl> CREATOR
+ = new Parcelable.Creator<TrackInfoImpl>() {
+ @Override
+ public TrackInfoImpl createFromParcel(Parcel in) {
+ return new TrackInfoImpl(in);
+ }
+
+ @Override
+ public TrackInfoImpl[] newArray(int size) {
+ return new TrackInfoImpl[size];
+ }
+ };
+
+ };
+
+ // We would like domain specific classes with more informative names than the `first` and `second`
+ // in generic Pair, but we would also like to avoid creating new/trivial classes. As a compromise
+ // we document the meanings of `first` and `second` here:
+ //
+ // Pair.first - inband track index; non-null iff representing an inband track.
+ // Pair.second - a SubtitleTrack registered with mSubtitleController; non-null iff representing
+ // an inband subtitle track or any out-of-band track (subtitle or timedtext).
+ private Vector<Pair<Integer, SubtitleTrack>> mIndexTrackPairs = new Vector<>();
+ private BitSet mInbandTrackIndices = new BitSet();
+
+ /**
+ * Returns a List of track information.
+ *
+ * @return List of track info. The total number of tracks is the array length.
+ * Must be called again if an external timed text source has been added after
+ * addTimedTextSource method is called.
+ * @throws IllegalStateException if it is called in an invalid state.
+ */
+ @Override
+ public List<TrackInfo> getTrackInfo() {
+ TrackInfoImpl trackInfo[] = getInbandTrackInfoImpl();
+ // add out-of-band tracks
+ synchronized (mIndexTrackPairs) {
+ TrackInfoImpl allTrackInfo[] = new TrackInfoImpl[mIndexTrackPairs.size()];
+ for (int i = 0; i < allTrackInfo.length; i++) {
+ Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i);
+ if (p.first != null) {
+ // inband track
+ allTrackInfo[i] = trackInfo[p.first];
+ } else {
+ SubtitleTrack track = p.second;
+ allTrackInfo[i] = new TrackInfoImpl(track.getTrackType(), track.getFormat());
+ }
+ }
+ return Arrays.asList(allTrackInfo);
+ }
+ }
+
+ private TrackInfoImpl[] getInbandTrackInfoImpl() throws IllegalStateException {
+ Parcel request = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ request.writeInt(INVOKE_ID_GET_TRACK_INFO);
+ invoke(request, reply);
+ TrackInfoImpl trackInfo[] = reply.createTypedArray(TrackInfoImpl.CREATOR);
+ return trackInfo;
+ } finally {
+ request.recycle();
+ reply.recycle();
+ }
+ }
+
+ /*
+ * A helper function to check if the mime type is supported by media framework.
+ */
+ private static boolean availableMimeTypeForExternalSource(String mimeType) {
+ if (MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mimeType)) {
+ return true;
+ }
+ return false;
+ }
+
+ private SubtitleController mSubtitleController;
+
+ /** @hide */
+ @Override
+ public void setSubtitleAnchor(
+ SubtitleController controller,
+ SubtitleController.Anchor anchor) {
+ // TODO: create SubtitleController in MediaPlayer2
+ mSubtitleController = controller;
+ mSubtitleController.setAnchor(anchor);
+ }
+
+ /**
+ * The private version of setSubtitleAnchor is used internally to set mSubtitleController if
+ * necessary when clients don't provide their own SubtitleControllers using the public version
+ * {@link #setSubtitleAnchor(SubtitleController, Anchor)} (e.g. {@link VideoView} provides one).
+ */
+ private synchronized void setSubtitleAnchor() {
+ if ((mSubtitleController == null) && (ActivityThread.currentApplication() != null)) {
+ final HandlerThread thread = new HandlerThread("SetSubtitleAnchorThread");
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ Context context = ActivityThread.currentApplication();
+ mSubtitleController = new SubtitleController(context, mTimeProvider, MediaPlayer2Impl.this);
+ mSubtitleController.setAnchor(new Anchor() {
+ @Override
+ public void setSubtitleWidget(RenderingWidget subtitleWidget) {
+ }
+
+ @Override
+ public Looper getSubtitleLooper() {
+ return Looper.getMainLooper();
+ }
+ });
+ thread.getLooper().quitSafely();
+ }
+ });
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ Log.w(TAG, "failed to join SetSubtitleAnchorThread");
+ }
+ }
+ }
+
+ private int mSelectedSubtitleTrackIndex = -1;
+ private Vector<InputStream> mOpenSubtitleSources;
+
+ private OnSubtitleDataListener mSubtitleDataListener = new OnSubtitleDataListener() {
+ @Override
+ public void onSubtitleData(MediaPlayer2 mp, SubtitleData data) {
+ int index = data.getTrackIndex();
+ synchronized (mIndexTrackPairs) {
+ for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) {
+ if (p.first != null && p.first == index && p.second != null) {
+ // inband subtitle track that owns data
+ SubtitleTrack track = p.second;
+ track.onData(data);
+ }
+ }
+ }
+ }
+ };
+
+ /** @hide */
+ @Override
+ public void onSubtitleTrackSelected(SubtitleTrack track) {
+ if (mSelectedSubtitleTrackIndex >= 0) {
+ try {
+ selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, false);
+ } catch (IllegalStateException e) {
+ }
+ mSelectedSubtitleTrackIndex = -1;
+ }
+ setOnSubtitleDataListener(null);
+ if (track == null) {
+ return;
+ }
+
+ synchronized (mIndexTrackPairs) {
+ for (Pair<Integer, SubtitleTrack> p : mIndexTrackPairs) {
+ if (p.first != null && p.second == track) {
+ // inband subtitle track that is selected
+ mSelectedSubtitleTrackIndex = p.first;
+ break;
+ }
+ }
+ }
+
+ if (mSelectedSubtitleTrackIndex >= 0) {
+ try {
+ selectOrDeselectInbandTrack(mSelectedSubtitleTrackIndex, true);
+ } catch (IllegalStateException e) {
+ }
+ setOnSubtitleDataListener(mSubtitleDataListener);
+ }
+ // no need to select out-of-band tracks
+ }
+
+ /** @hide */
+ @Override
+ public void addSubtitleSource(InputStream is, MediaFormat format)
+ throws IllegalStateException
+ {
+ final InputStream fIs = is;
+ final MediaFormat fFormat = format;
+
+ if (is != null) {
+ // Ensure all input streams are closed. It is also a handy
+ // way to implement timeouts in the future.
+ synchronized(mOpenSubtitleSources) {
+ mOpenSubtitleSources.add(is);
+ }
+ } else {
+ Log.w(TAG, "addSubtitleSource called with null InputStream");
+ }
+
+ getMediaTimeProvider();
+
+ // process each subtitle in its own thread
+ final HandlerThread thread = new HandlerThread("SubtitleReadThread",
+ Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE);
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+ handler.post(new Runnable() {
+ private int addTrack() {
+ if (fIs == null || mSubtitleController == null) {
+ return MEDIA_INFO_UNSUPPORTED_SUBTITLE;
+ }
+
+ SubtitleTrack track = mSubtitleController.addTrack(fFormat);
+ if (track == null) {
+ return MEDIA_INFO_UNSUPPORTED_SUBTITLE;
+ }
+
+ // TODO: do the conversion in the subtitle track
+ Scanner scanner = new Scanner(fIs, "UTF-8");
+ String contents = scanner.useDelimiter("\\A").next();
+ synchronized(mOpenSubtitleSources) {
+ mOpenSubtitleSources.remove(fIs);
+ }
+ scanner.close();
+ synchronized (mIndexTrackPairs) {
+ mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track));
+ }
+ Handler h = mTimeProvider.mEventHandler;
+ int what = TimeProvider.NOTIFY;
+ int arg1 = TimeProvider.NOTIFY_TRACK_DATA;
+ Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, contents.getBytes());
+ Message m = h.obtainMessage(what, arg1, 0, trackData);
+ h.sendMessage(m);
+ return MEDIA_INFO_EXTERNAL_METADATA_UPDATE;
+ }
+
+ public void run() {
+ int res = addTrack();
+ if (mEventHandler != null) {
+ Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null);
+ mEventHandler.sendMessage(m);
+ }
+ thread.getLooper().quitSafely();
+ }
+ });
+ }
+
+ private void scanInternalSubtitleTracks() {
+ setSubtitleAnchor();
+
+ populateInbandTracks();
+
+ if (mSubtitleController != null) {
+ mSubtitleController.selectDefaultTrack();
+ }
+ }
+
+ private void populateInbandTracks() {
+ TrackInfoImpl[] tracks = getInbandTrackInfoImpl();
+ synchronized (mIndexTrackPairs) {
+ for (int i = 0; i < tracks.length; i++) {
+ if (mInbandTrackIndices.get(i)) {
+ continue;
+ } else {
+ mInbandTrackIndices.set(i);
+ }
+
+ // newly appeared inband track
+ if (tracks[i].getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
+ SubtitleTrack track = mSubtitleController.addTrack(
+ tracks[i].getFormat());
+ mIndexTrackPairs.add(Pair.create(i, track));
+ } else {
+ mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(i, null));
+ }
+ }
+ }
+ }
+
+ /* TODO: Limit the total number of external timed text source to a reasonable number.
+ */
+ /**
+ * Adds an external timed text source file.
+ *
+ * Currently supported format is SubRip with the file extension .srt, case insensitive.
+ * Note that a single external timed text source may contain multiple tracks in it.
+ * One can find the total number of available tracks using {@link #getTrackInfo()} to see what
+ * additional tracks become available after this method call.
+ *
+ * @param path The file path of external timed text source file.
+ * @param mimeType The mime type of the file. Must be one of the mime types listed above.
+ * @throws IOException if the file cannot be accessed or is corrupted.
+ * @throws IllegalArgumentException if the mimeType is not supported.
+ * @throws IllegalStateException if called in an invalid state.
+ * @hide
+ */
+ @Override
+ public void addTimedTextSource(String path, String mimeType)
+ throws IOException {
+ if (!availableMimeTypeForExternalSource(mimeType)) {
+ final String msg = "Illegal mimeType for timed text source: " + mimeType;
+ throw new IllegalArgumentException(msg);
+ }
+
+ File file = new File(path);
+ if (file.exists()) {
+ FileInputStream is = new FileInputStream(file);
+ FileDescriptor fd = is.getFD();
+ addTimedTextSource(fd, mimeType);
+ is.close();
+ } else {
+ // We do not support the case where the path is not a file.
+ throw new IOException(path);
+ }
+ }
+
+
+ /**
+ * Adds an external timed text source file (Uri).
+ *
+ * Currently supported format is SubRip with the file extension .srt, case insensitive.
+ * Note that a single external timed text source may contain multiple tracks in it.
+ * One can find the total number of available tracks using {@link #getTrackInfo()} to see what
+ * additional tracks become available after this method call.
+ *
+ * @param context the Context to use when resolving the Uri
+ * @param uri the Content URI of the data you want to play
+ * @param mimeType The mime type of the file. Must be one of the mime types listed above.
+ * @throws IOException if the file cannot be accessed or is corrupted.
+ * @throws IllegalArgumentException if the mimeType is not supported.
+ * @throws IllegalStateException if called in an invalid state.
+ * @hide
+ */
+ @Override
+ public void addTimedTextSource(Context context, Uri uri, String mimeType)
+ throws IOException {
+ String scheme = uri.getScheme();
+ if(scheme == null || scheme.equals("file")) {
+ addTimedTextSource(uri.getPath(), mimeType);
+ return;
+ }
+
+ AssetFileDescriptor fd = null;
+ try {
+ ContentResolver resolver = context.getContentResolver();
+ fd = resolver.openAssetFileDescriptor(uri, "r");
+ if (fd == null) {
+ return;
+ }
+ addTimedTextSource(fd.getFileDescriptor(), mimeType);
+ return;
+ } catch (SecurityException ex) {
+ } catch (IOException ex) {
+ } finally {
+ if (fd != null) {
+ fd.close();
+ }
+ }
+ }
+
+ /**
+ * Adds an external timed text source file (FileDescriptor).
+ *
+ * It is the caller's responsibility to close the file descriptor.
+ * It is safe to do so as soon as this call returns.
+ *
+ * Currently supported format is SubRip. Note that a single external timed text source may
+ * contain multiple tracks in it. One can find the total number of available tracks
+ * using {@link #getTrackInfo()} to see what additional tracks become available
+ * after this method call.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @param mimeType The mime type of the file. Must be one of the mime types listed above.
+ * @throws IllegalArgumentException if the mimeType is not supported.
+ * @throws IllegalStateException if called in an invalid state.
+ * @hide
+ */
+ @Override
+ public void addTimedTextSource(FileDescriptor fd, String mimeType) {
+ // intentionally less than LONG_MAX
+ addTimedTextSource(fd, 0, 0x7ffffffffffffffL, mimeType);
+ }
+
+ /**
+ * Adds an external timed text file (FileDescriptor).
+ *
+ * It is the caller's responsibility to close the file descriptor.
+ * It is safe to do so as soon as this call returns.
+ *
+ * Currently supported format is SubRip. Note that a single external timed text source may
+ * contain multiple tracks in it. One can find the total number of available tracks
+ * using {@link #getTrackInfo()} to see what additional tracks become available
+ * after this method call.
+ *
+ * @param fd the FileDescriptor for the file you want to play
+ * @param offset the offset into the file where the data to be played starts, in bytes
+ * @param length the length in bytes of the data to be played
+ * @param mime The mime type of the file. Must be one of the mime types listed above.
+ * @throws IllegalArgumentException if the mimeType is not supported.
+ * @throws IllegalStateException if called in an invalid state.
+ * @hide
+ */
+ @Override
+ public void addTimedTextSource(FileDescriptor fd, long offset, long length, String mime) {
+ if (!availableMimeTypeForExternalSource(mime)) {
+ throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mime);
+ }
+
+ final FileDescriptor dupedFd;
+ try {
+ dupedFd = Os.dup(fd);
+ } catch (ErrnoException ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ throw new RuntimeException(ex);
+ }
+
+ final MediaFormat fFormat = new MediaFormat();
+ fFormat.setString(MediaFormat.KEY_MIME, mime);
+ fFormat.setInteger(MediaFormat.KEY_IS_TIMED_TEXT, 1);
+
+ // A MediaPlayer2 created by a VideoView should already have its mSubtitleController set.
+ if (mSubtitleController == null) {
+ setSubtitleAnchor();
+ }
+
+ if (!mSubtitleController.hasRendererFor(fFormat)) {
+ // test and add not atomic
+ Context context = ActivityThread.currentApplication();
+ mSubtitleController.registerRenderer(new SRTRenderer(context, mEventHandler));
+ }
+ final SubtitleTrack track = mSubtitleController.addTrack(fFormat);
+ synchronized (mIndexTrackPairs) {
+ mIndexTrackPairs.add(Pair.<Integer, SubtitleTrack>create(null, track));
+ }
+
+ getMediaTimeProvider();
+
+ final long offset2 = offset;
+ final long length2 = length;
+ final HandlerThread thread = new HandlerThread(
+ "TimedTextReadThread",
+ Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE);
+ thread.start();
+ Handler handler = new Handler(thread.getLooper());
+ handler.post(new Runnable() {
+ private int addTrack() {
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try {
+ Os.lseek(dupedFd, offset2, OsConstants.SEEK_SET);
+ byte[] buffer = new byte[4096];
+ for (long total = 0; total < length2;) {
+ int bytesToRead = (int) Math.min(buffer.length, length2 - total);
+ int bytes = IoBridge.read(dupedFd, buffer, 0, bytesToRead);
+ if (bytes < 0) {
+ break;
+ } else {
+ bos.write(buffer, 0, bytes);
+ total += bytes;
+ }
+ }
+ Handler h = mTimeProvider.mEventHandler;
+ int what = TimeProvider.NOTIFY;
+ int arg1 = TimeProvider.NOTIFY_TRACK_DATA;
+ Pair<SubtitleTrack, byte[]> trackData = Pair.create(track, bos.toByteArray());
+ Message m = h.obtainMessage(what, arg1, 0, trackData);
+ h.sendMessage(m);
+ return MEDIA_INFO_EXTERNAL_METADATA_UPDATE;
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage(), e);
+ return MEDIA_INFO_TIMED_TEXT_ERROR;
+ } finally {
+ try {
+ Os.close(dupedFd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ }
+ }
+
+ public void run() {
+ int res = addTrack();
+ if (mEventHandler != null) {
+ Message m = mEventHandler.obtainMessage(MEDIA_INFO, res, 0, null);
+ mEventHandler.sendMessage(m);
+ }
+ thread.getLooper().quitSafely();
+ }
+ });
+ }
+
+ /**
+ * Returns the index of the audio, video, or subtitle track currently selected for playback,
+ * The return value is an index into the array returned by {@link #getTrackInfo()}, and can
+ * be used in calls to {@link #selectTrack(int)} or {@link #deselectTrack(int)}.
+ *
+ * @param trackType should be one of {@link TrackInfo#MEDIA_TRACK_TYPE_VIDEO},
+ * {@link TrackInfo#MEDIA_TRACK_TYPE_AUDIO}, or
+ * {@link TrackInfo#MEDIA_TRACK_TYPE_SUBTITLE}
+ * @return index of the audio, video, or subtitle track currently selected for playback;
+ * a negative integer is returned when there is no selected track for {@code trackType} or
+ * when {@code trackType} is not one of audio, video, or subtitle.
+ * @throws IllegalStateException if called after {@link #close()}
+ *
+ * @see #getTrackInfo()
+ * @see #selectTrack(int)
+ * @see #deselectTrack(int)
+ */
+ @Override
+ public int getSelectedTrack(int trackType) {
+ if (mSubtitleController != null
+ && (trackType == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE
+ || trackType == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT)) {
+ SubtitleTrack subtitleTrack = mSubtitleController.getSelectedTrack();
+ if (subtitleTrack != null) {
+ synchronized (mIndexTrackPairs) {
+ for (int i = 0; i < mIndexTrackPairs.size(); i++) {
+ Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i);
+ if (p.second == subtitleTrack && subtitleTrack.getTrackType() == trackType) {
+ return i;
+ }
+ }
+ }
+ }
+ }
+
+ Parcel request = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ request.writeInt(INVOKE_ID_GET_SELECTED_TRACK);
+ request.writeInt(trackType);
+ invoke(request, reply);
+ int inbandTrackIndex = reply.readInt();
+ synchronized (mIndexTrackPairs) {
+ for (int i = 0; i < mIndexTrackPairs.size(); i++) {
+ Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i);
+ if (p.first != null && p.first == inbandTrackIndex) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ } finally {
+ request.recycle();
+ reply.recycle();
+ }
+ }
+
+ /**
+ * Selects a track.
+ * <p>
+ * If a MediaPlayer2 is in invalid state, it throws an IllegalStateException exception.
+ * If a MediaPlayer2 is in <em>Started</em> state, the selected track is presented immediately.
+ * If a MediaPlayer2 is not in Started state, it just marks the track to be played.
+ * </p>
+ * <p>
+ * In any valid state, if it is called multiple times on the same type of track (ie. Video,
+ * Audio, Timed Text), the most recent one will be chosen.
+ * </p>
+ * <p>
+ * The first audio and video tracks are selected by default if available, even though
+ * this method is not called. However, no timed text track will be selected until
+ * this function is called.
+ * </p>
+ * <p>
+ * Currently, only timed text tracks or audio tracks can be selected via this method.
+ * In addition, the support for selecting an audio track at runtime is pretty limited
+ * in that an audio track can only be selected in the <em>Prepared</em> state.
+ * </p>
+ * @param index the index of the track to be selected. The valid range of the index
+ * is 0..total number of track - 1. The total number of tracks as well as the type of
+ * each individual track can be found by calling {@link #getTrackInfo()} method.
+ * @throws IllegalStateException if called in an invalid state.
+ *
+ * @see android.media.MediaPlayer2#getTrackInfo
+ */
+ @Override
+ public void selectTrack(int index) {
+ selectOrDeselectTrack(index, true /* select */);
+ }
+
+ /**
+ * Deselect a track.
+ * <p>
+ * Currently, the track must be a timed text track and no audio or video tracks can be
+ * deselected. If the timed text track identified by index has not been
+ * selected before, it throws an exception.
+ * </p>
+ * @param index the index of the track to be deselected. The valid range of the index
+ * is 0..total number of tracks - 1. The total number of tracks as well as the type of
+ * each individual track can be found by calling {@link #getTrackInfo()} method.
+ * @throws IllegalStateException if called in an invalid state.
+ *
+ * @see android.media.MediaPlayer2#getTrackInfo
+ */
+ @Override
+ public void deselectTrack(int index) {
+ selectOrDeselectTrack(index, false /* select */);
+ }
+
+ private void selectOrDeselectTrack(int index, boolean select)
+ throws IllegalStateException {
+ // handle subtitle track through subtitle controller
+ populateInbandTracks();
+
+ Pair<Integer,SubtitleTrack> p = null;
+ try {
+ p = mIndexTrackPairs.get(index);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // ignore bad index
+ return;
+ }
+
+ SubtitleTrack track = p.second;
+ if (track == null) {
+ // inband (de)select
+ selectOrDeselectInbandTrack(p.first, select);
+ return;
+ }
+
+ if (mSubtitleController == null) {
+ return;
+ }
+
+ if (!select) {
+ // out-of-band deselect
+ if (mSubtitleController.getSelectedTrack() == track) {
+ mSubtitleController.selectTrack(null);
+ } else {
+ Log.w(TAG, "trying to deselect track that was not selected");
+ }
+ return;
+ }
+
+ // out-of-band select
+ if (track.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
+ int ttIndex = getSelectedTrack(TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT);
+ synchronized (mIndexTrackPairs) {
+ if (ttIndex >= 0 && ttIndex < mIndexTrackPairs.size()) {
+ Pair<Integer,SubtitleTrack> p2 = mIndexTrackPairs.get(ttIndex);
+ if (p2.first != null && p2.second == null) {
+ // deselect inband counterpart
+ selectOrDeselectInbandTrack(p2.first, false);
+ }
+ }
+ }
+ }
+ mSubtitleController.selectTrack(track);
+ }
+
+ private void selectOrDeselectInbandTrack(int index, boolean select)
+ throws IllegalStateException {
+ Parcel request = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ request.writeInt(select? INVOKE_ID_SELECT_TRACK: INVOKE_ID_DESELECT_TRACK);
+ request.writeInt(index);
+ invoke(request, reply);
+ } finally {
+ request.recycle();
+ reply.recycle();
+ }
+ }
+
+ /**
+ * Sets the target UDP re-transmit endpoint for the low level player.
+ * Generally, the address portion of the endpoint is an IP multicast
+ * address, although a unicast address would be equally valid. When a valid
+ * retransmit endpoint has been set, the media player will not decode and
+ * render the media presentation locally. Instead, the player will attempt
+ * to re-multiplex its media data using the Android@Home RTP profile and
+ * re-transmit to the target endpoint. Receiver devices (which may be
+ * either the same as the transmitting device or different devices) may
+ * instantiate, prepare, and start a receiver player using a setDataSource
+ * URL of the form...
+ *
+ * aahRX://<multicastIP>:<port>
+ *
+ * to receive, decode and render the re-transmitted content.
+ *
+ * setRetransmitEndpoint may only be called before setDataSource has been
+ * called; while the player is in the Idle state.
+ *
+ * @param endpoint the address and UDP port of the re-transmission target or
+ * null if no re-transmission is to be performed.
+ * @throws IllegalStateException if it is called in an invalid state
+ * @throws IllegalArgumentException if the retransmit endpoint is supplied,
+ * but invalid.
+ *
+ * {@hide} pending API council
+ */
+ @Override
+ public void setRetransmitEndpoint(InetSocketAddress endpoint)
+ throws IllegalStateException, IllegalArgumentException
+ {
+ String addrString = null;
+ int port = 0;
+
+ if (null != endpoint) {
+ addrString = endpoint.getAddress().getHostAddress();
+ port = endpoint.getPort();
+ }
+
+ int ret = native_setRetransmitEndpoint(addrString, port);
+ if (ret != 0) {
+ throw new IllegalArgumentException("Illegal re-transmit endpoint; native ret " + ret);
+ }
+ }
+
+ private native final int native_setRetransmitEndpoint(String addrString, int port);
+
+ /**
+ * Releases the resources held by this {@code MediaPlayer2} object.
+ *
+ * It is considered good practice to call this method when you're
+ * done using the MediaPlayer2. In particular, whenever an Activity
+ * of an application is paused (its onPause() method is called),
+ * or stopped (its onStop() method is called), this method should be
+ * invoked to release the MediaPlayer2 object, unless the application
+ * has a special need to keep the object around. In addition to
+ * unnecessary resources (such as memory and instances of codecs)
+ * being held, failure to call this method immediately if a
+ * MediaPlayer2 object is no longer needed may also lead to
+ * continuous battery consumption for mobile devices, and playback
+ * failure for other applications if no multiple instances of the
+ * same codec are supported on a device. Even if multiple instances
+ * of the same codec are supported, some performance degradation
+ * may be expected when unnecessary multiple instances are used
+ * at the same time.
+ *
+ * {@code close()} may be safely called after a prior {@code close()}.
+ * This class implements the Java {@code AutoCloseable} interface and
+ * may be used with try-with-resources.
+ */
+ @Override
+ public void close() {
+ synchronized (mGuard) {
+ release();
+ }
+ }
+
+ // Have to declare protected for finalize() since it is protected
+ // in the base class Object.
+ @Override
+ protected void finalize() throws Throwable {
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ }
+
+ close();
+ native_finalize();
+ }
+
+ private void release() {
+ stayAwake(false);
+ updateSurfaceScreenOn();
+ synchronized (mEventCbLock) {
+ mEventCb = null;
+ mEventExec = null;
+ }
+ if (mTimeProvider != null) {
+ mTimeProvider.close();
+ mTimeProvider = null;
+ }
+ mOnSubtitleDataListener = null;
+
+ // Modular DRM clean up
+ mOnDrmConfigHelper = null;
+ synchronized (mDrmEventCbLock) {
+ mDrmEventCb = null;
+ mDrmEventExec = null;
+ }
+ resetDrmState();
+
+ _release();
+ }
+
+ private native void _release();
+
+ /* Do not change these values without updating their counterparts
+ * in include/media/mediaplayer2.h!
+ */
+ private static final int MEDIA_NOP = 0; // interface test message
+ private static final int MEDIA_PREPARED = 1;
+ private static final int MEDIA_PLAYBACK_COMPLETE = 2;
+ private static final int MEDIA_BUFFERING_UPDATE = 3;
+ private static final int MEDIA_SEEK_COMPLETE = 4;
+ private static final int MEDIA_SET_VIDEO_SIZE = 5;
+ private static final int MEDIA_STARTED = 6;
+ private static final int MEDIA_PAUSED = 7;
+ private static final int MEDIA_STOPPED = 8;
+ private static final int MEDIA_SKIPPED = 9;
+ private static final int MEDIA_NOTIFY_TIME = 98;
+ private static final int MEDIA_TIMED_TEXT = 99;
+ private static final int MEDIA_ERROR = 100;
+ private static final int MEDIA_INFO = 200;
+ private static final int MEDIA_SUBTITLE_DATA = 201;
+ private static final int MEDIA_META_DATA = 202;
+ private static final int MEDIA_DRM_INFO = 210;
+ private static final int MEDIA_AUDIO_ROUTING_CHANGED = 10000;
+
+ private TimeProvider mTimeProvider;
+
+ /** @hide */
+ @Override
+ public MediaTimeProvider getMediaTimeProvider() {
+ if (mTimeProvider == null) {
+ mTimeProvider = new TimeProvider(this);
+ }
+ return mTimeProvider;
+ }
+
+ private class EventHandler extends Handler {
+ private MediaPlayer2Impl mMediaPlayer;
+
+ public EventHandler(MediaPlayer2Impl mp, Looper looper) {
+ super(looper);
+ mMediaPlayer = mp;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mMediaPlayer.mNativeContext == 0) {
+ Log.w(TAG, "mediaplayer2 went away with unhandled events");
+ return;
+ }
+ final Executor eventExec;
+ final EventCallback eventCb;
+ synchronized (mEventCbLock) {
+ eventExec = mEventExec;
+ eventCb = mEventCb;
+ }
+ final Executor drmEventExec;
+ final DrmEventCallback drmEventCb;
+ synchronized (mDrmEventCbLock) {
+ drmEventExec = mDrmEventExec;
+ drmEventCb = mDrmEventCb;
+ }
+ switch(msg.what) {
+ case MEDIA_PREPARED:
+ try {
+ scanInternalSubtitleTracks();
+ } catch (RuntimeException e) {
+ // send error message instead of crashing;
+ // send error message instead of inlining a call to onError
+ // to avoid code duplication.
+ Message msg2 = obtainMessage(
+ MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
+ sendMessage(msg2);
+ }
+
+ if (eventCb != null && eventExec != null) {
+ eventExec.execute(() -> eventCb.onInfo(
+ mMediaPlayer, 0, MEDIA_INFO_PREPARED, 0));
+ }
+ return;
+
+ case MEDIA_DRM_INFO:
+ Log.v(TAG, "MEDIA_DRM_INFO " + mDrmEventCb);
+
+ if (msg.obj == null) {
+ Log.w(TAG, "MEDIA_DRM_INFO msg.obj=NULL");
+ } else if (msg.obj instanceof Parcel) {
+ if (drmEventExec != null && drmEventCb != null) {
+ // The parcel was parsed already in postEventFromNative
+ final DrmInfoImpl drmInfo;
+
+ synchronized (mDrmLock) {
+ if (mDrmInfoImpl != null) {
+ drmInfo = mDrmInfoImpl.makeCopy();
+ } else {
+ drmInfo = null;
+ }
+ }
+
+ // notifying the client outside the lock
+ if (drmInfo != null) {
+ drmEventExec.execute(() -> drmEventCb.onDrmInfo(mMediaPlayer, drmInfo));
+ }
+ }
+ } else {
+ Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + msg.obj);
+ }
+ return;
+
+ case MEDIA_PLAYBACK_COMPLETE:
+ if (eventCb != null && eventExec != null) {
+ eventExec.execute(() -> eventCb.onInfo(
+ mMediaPlayer, 0, MEDIA_INFO_PLAYBACK_COMPLETE, 0));
+ }
+ stayAwake(false);
+ return;
+
+ case MEDIA_STOPPED:
+ {
+ TimeProvider timeProvider = mTimeProvider;
+ if (timeProvider != null) {
+ timeProvider.onStopped();
+ }
+ }
+ break;
+
+ case MEDIA_STARTED:
+ case MEDIA_PAUSED:
+ {
+ TimeProvider timeProvider = mTimeProvider;
+ if (timeProvider != null) {
+ timeProvider.onPaused(msg.what == MEDIA_PAUSED);
+ }
+ }
+ break;
+
+ case MEDIA_BUFFERING_UPDATE:
+ if (eventCb != null && eventExec != null) {
+ final int percent = msg.arg1;
+ eventExec.execute(() -> eventCb.onBufferingUpdate(mMediaPlayer, 0, percent));
+ }
+ return;
+
+ case MEDIA_SEEK_COMPLETE:
+ if (eventCb != null && eventExec != null) {
+ eventExec.execute(() -> eventCb.onInfo(
+ mMediaPlayer, 0, MEDIA_INFO_COMPLETE_CALL_SEEK, 0));
+ }
+ // fall through
+
+ case MEDIA_SKIPPED:
+ {
+ TimeProvider timeProvider = mTimeProvider;
+ if (timeProvider != null) {
+ timeProvider.onSeekComplete(mMediaPlayer);
+ }
+ }
+ return;
+
+ case MEDIA_SET_VIDEO_SIZE:
+ if (eventCb != null && eventExec != null) {
+ final int width = msg.arg1;
+ final int height = msg.arg2;
+ eventExec.execute(() -> eventCb.onVideoSizeChanged(
+ mMediaPlayer, 0, width, height));
+ }
+ return;
+
+ case MEDIA_ERROR:
+ Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
+ if (eventCb != null && eventExec != null) {
+ final int what = msg.arg1;
+ final int extra = msg.arg2;
+ eventExec.execute(() -> eventCb.onError(mMediaPlayer, 0, what, extra));
+ eventExec.execute(() -> eventCb.onInfo(
+ mMediaPlayer, 0, MEDIA_INFO_PLAYBACK_COMPLETE, 0));
+ }
+ stayAwake(false);
+ return;
+
+ case MEDIA_INFO:
+ switch (msg.arg1) {
+ case MEDIA_INFO_VIDEO_TRACK_LAGGING:
+ Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")");
+ break;
+ case MEDIA_INFO_METADATA_UPDATE:
+ try {
+ scanInternalSubtitleTracks();
+ } catch (RuntimeException e) {
+ Message msg2 = obtainMessage(
+ MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null);
+ sendMessage(msg2);
+ }
+ // fall through
+
+ case MEDIA_INFO_EXTERNAL_METADATA_UPDATE:
+ msg.arg1 = MEDIA_INFO_METADATA_UPDATE;
+ // update default track selection
+ if (mSubtitleController != null) {
+ mSubtitleController.selectDefaultTrack();
+ }
+ break;
+ case MEDIA_INFO_BUFFERING_START:
+ case MEDIA_INFO_BUFFERING_END:
+ TimeProvider timeProvider = mTimeProvider;
+ if (timeProvider != null) {
+ timeProvider.onBuffering(msg.arg1 == MEDIA_INFO_BUFFERING_START);
+ }
+ break;
+ }
+
+ if (eventCb != null && eventExec != null) {
+ final int what = msg.arg1;
+ final int extra = msg.arg2;
+ eventExec.execute(() -> eventCb.onInfo(mMediaPlayer, 0, what, extra));
+ }
+ // No real default action so far.
+ return;
+
+ case MEDIA_NOTIFY_TIME:
+ TimeProvider timeProvider = mTimeProvider;
+ if (timeProvider != null) {
+ timeProvider.onNotifyTime();
+ }
+ return;
+
+ case MEDIA_TIMED_TEXT:
+ if (eventCb == null || eventExec == null) {
+ return;
+ }
+ if (msg.obj == null) {
+ eventExec.execute(() -> eventCb.onTimedText(mMediaPlayer, 0, null));
+ } else {
+ if (msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel)msg.obj;
+ TimedText text = new TimedText(parcel);
+ parcel.recycle();
+ eventExec.execute(() -> eventCb.onTimedText(mMediaPlayer, 0, text));
+ }
+ }
+ return;
+
+ case MEDIA_SUBTITLE_DATA:
+ OnSubtitleDataListener onSubtitleDataListener = mOnSubtitleDataListener;
+ if (onSubtitleDataListener == null) {
+ return;
+ }
+ if (msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel) msg.obj;
+ SubtitleData data = new SubtitleData(parcel);
+ parcel.recycle();
+ onSubtitleDataListener.onSubtitleData(mMediaPlayer, data);
+ }
+ return;
+
+ case MEDIA_META_DATA:
+ if (eventCb == null || eventExec == null) {
+ return;
+ }
+ if (msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel) msg.obj;
+ TimedMetaData data = TimedMetaData.createTimedMetaDataFromParcel(parcel);
+ parcel.recycle();
+ eventExec.execute(() -> eventCb.onTimedMetaDataAvailable(
+ mMediaPlayer, 0, data));
+ }
+ return;
+
+ case MEDIA_NOP: // interface test message - ignore
+ break;
+
+ case MEDIA_AUDIO_ROUTING_CHANGED:
+ AudioManager.resetAudioPortGeneration();
+ synchronized (mRoutingChangeListeners) {
+ for (NativeRoutingEventHandlerDelegate delegate
+ : mRoutingChangeListeners.values()) {
+ delegate.notifyClient();
+ }
+ }
+ return;
+
+ default:
+ Log.e(TAG, "Unknown message type " + msg.what);
+ return;
+ }
+ }
+ }
+
+ /*
+ * Called from native code when an interesting event happens. This method
+ * just uses the EventHandler system to post the event back to the main app thread.
+ * We use a weak reference to the original MediaPlayer2 object so that the native
+ * code is safe from the object disappearing from underneath it. (This is
+ * the cookie passed to native_setup().)
+ */
+ private static void postEventFromNative(Object mediaplayer2_ref,
+ int what, int arg1, int arg2, Object obj)
+ {
+ final MediaPlayer2Impl mp = (MediaPlayer2Impl)((WeakReference)mediaplayer2_ref).get();
+ if (mp == null) {
+ return;
+ }
+
+ switch (what) {
+ case MEDIA_INFO:
+ if (arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // this acquires the wakelock if needed, and sets the client side state
+ mp.play();
+ }
+ }).start();
+ Thread.yield();
+ }
+ break;
+
+ case MEDIA_DRM_INFO:
+ // We need to derive mDrmInfoImpl before prepare() returns so processing it here
+ // before the notification is sent to EventHandler below. EventHandler runs in the
+ // notification looper so its handleMessage might process the event after prepare()
+ // has returned.
+ Log.v(TAG, "postEventFromNative MEDIA_DRM_INFO");
+ if (obj instanceof Parcel) {
+ Parcel parcel = (Parcel)obj;
+ DrmInfoImpl drmInfo = new DrmInfoImpl(parcel);
+ synchronized (mp.mDrmLock) {
+ mp.mDrmInfoImpl = drmInfo;
+ }
+ } else {
+ Log.w(TAG, "MEDIA_DRM_INFO msg.obj of unexpected type " + obj);
+ }
+ break;
+
+ case MEDIA_PREPARED:
+ // By this time, we've learned about DrmInfo's presence or absence. This is meant
+ // mainly for prepareAsync() use case. For prepare(), this still can run to a race
+ // condition b/c MediaPlayerNative releases the prepare() lock before calling notify
+ // so we also set mDrmInfoResolved in prepare().
+ synchronized (mp.mDrmLock) {
+ mp.mDrmInfoResolved = true;
+ }
+ break;
+
+ }
+
+ if (mp.mEventHandler != null) {
+ Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
+ mp.mEventHandler.sendMessage(m);
+ }
+ }
+
+ private Executor mEventExec;
+ private EventCallback mEventCb;
+ private final Object mEventCbLock = new Object();
+
+ /**
+ * Register a callback to be invoked when the media source is ready
+ * for playback.
+ *
+ * @param eventCallback the callback that will be run
+ * @param executor the executor through which the callback should be invoked
+ */
+ @Override
+ public void registerEventCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull EventCallback eventCallback) {
+ if (eventCallback == null) {
+ throw new IllegalArgumentException("Illegal null EventCallback");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException("Illegal null Executor for the EventCallback");
+ }
+ synchronized (mEventCbLock) {
+ // TODO: support multiple callbacks.
+ mEventExec = executor;
+ mEventCb = eventCallback;
+ }
+ }
+
+ /**
+ * Unregisters an {@link EventCallback}.
+ *
+ * @param callback an {@link EventCallback} to unregister
+ */
+ @Override
+ public void unregisterEventCallback(EventCallback callback) {
+ synchronized (mEventCbLock) {
+ if (callback == mEventCb) {
+ mEventExec = null;
+ mEventCb = null;
+ }
+ }
+ }
+
+ /**
+ * Register a callback to be invoked when a track has data available.
+ *
+ * @param listener the callback that will be run
+ *
+ * @hide
+ */
+ @Override
+ public void setOnSubtitleDataListener(OnSubtitleDataListener listener) {
+ mOnSubtitleDataListener = listener;
+ }
+
+ private OnSubtitleDataListener mOnSubtitleDataListener;
+
+
+ // Modular DRM begin
+
+ /**
+ * Register a callback to be invoked for configuration of the DRM object before
+ * the session is created.
+ * The callback will be invoked synchronously during the execution
+ * of {@link #prepareDrm(UUID uuid)}.
+ *
+ * @param listener the callback that will be run
+ */
+ @Override
+ public void setOnDrmConfigHelper(OnDrmConfigHelper listener)
+ {
+ synchronized (mDrmLock) {
+ mOnDrmConfigHelper = listener;
+ } // synchronized
+ }
+
+ private OnDrmConfigHelper mOnDrmConfigHelper;
+
+ private Executor mDrmEventExec;
+ private DrmEventCallback mDrmEventCb;
+ private final Object mDrmEventCbLock = new Object();
+
+ /**
+ * Register a callback to be invoked when the media source is ready
+ * for playback.
+ *
+ * @param eventCallback the callback that will be run
+ * @param executor the executor through which the callback should be invoked
+ */
+ @Override
+ public void registerDrmEventCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull DrmEventCallback eventCallback) {
+ if (eventCallback == null) {
+ throw new IllegalArgumentException("Illegal null EventCallback");
+ }
+ if (executor == null) {
+ throw new IllegalArgumentException("Illegal null Executor for the EventCallback");
+ }
+ synchronized (mDrmEventCbLock) {
+ // TODO: support multiple callbacks.
+ mDrmEventExec = executor;
+ mDrmEventCb = eventCallback;
+ }
+ }
+
+ /**
+ * Unregisters a {@link DrmEventCallback}.
+ *
+ * @param callback a {@link DrmEventCallback} to unregister
+ */
+ @Override
+ public void unregisterDrmEventCallback(DrmEventCallback callback) {
+ synchronized (mDrmEventCbLock) {
+ if (callback == mDrmEventCb) {
+ mDrmEventExec = null;
+ mDrmEventCb = null;
+ }
+ }
+ }
+
+
+ /**
+ * Retrieves the DRM Info associated with the current source
+ *
+ * @throws IllegalStateException if called before prepare()
+ */
+ @Override
+ public DrmInfo getDrmInfo() {
+ DrmInfoImpl drmInfo = null;
+
+ // there is not much point if the app calls getDrmInfo within an OnDrmInfoListenet;
+ // regardless below returns drmInfo anyway instead of raising an exception
+ synchronized (mDrmLock) {
+ if (!mDrmInfoResolved && mDrmInfoImpl == null) {
+ final String msg = "The Player has not been prepared yet";
+ Log.v(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mDrmInfoImpl != null) {
+ drmInfo = mDrmInfoImpl.makeCopy();
+ }
+ } // synchronized
+
+ return drmInfo;
+ }
+
+
+ /**
+ * Prepares the DRM for the current source
+ * <p>
+ * If {@code OnDrmConfigHelper} is registered, it will be called during
+ * preparation to allow configuration of the DRM properties before opening the
+ * DRM session. Note that the callback is called synchronously in the thread that called
+ * {@code prepareDrm}. It should be used only for a series of {@code getDrmPropertyString}
+ * and {@code setDrmPropertyString} calls and refrain from any lengthy operation.
+ * <p>
+ * If the device has not been provisioned before, this call also provisions the device
+ * which involves accessing the provisioning server and can take a variable time to
+ * complete depending on the network connectivity.
+ * If {@code OnDrmPreparedListener} is registered, prepareDrm() runs in non-blocking
+ * mode by launching the provisioning in the background and returning. The listener
+ * will be called when provisioning and preparation has finished. If a
+ * {@code OnDrmPreparedListener} is not registered, prepareDrm() waits till provisioning
+ * and preparation has finished, i.e., runs in blocking mode.
+ * <p>
+ * If {@code OnDrmPreparedListener} is registered, it is called to indicate the DRM
+ * session being ready. The application should not make any assumption about its call
+ * sequence (e.g., before or after prepareDrm returns), or the thread context that will
+ * execute the listener (unless the listener is registered with a handler thread).
+ * <p>
+ *
+ * @param uuid The UUID of the crypto scheme. If not known beforehand, it can be retrieved
+ * from the source through {@code getDrmInfo} or registering a {@code onDrmInfoListener}.
+ *
+ * @throws IllegalStateException if called before prepare(), or the DRM was
+ * prepared already
+ * @throws UnsupportedSchemeException if the crypto scheme is not supported
+ * @throws ResourceBusyException if required DRM resources are in use
+ * @throws ProvisioningNetworkErrorException if provisioning is required but failed due to a
+ * network error
+ * @throws ProvisioningServerErrorException if provisioning is required but failed due to
+ * the request denied by the provisioning server
+ */
+ @Override
+ public void prepareDrm(@NonNull UUID uuid)
+ throws UnsupportedSchemeException, ResourceBusyException,
+ ProvisioningNetworkErrorException, ProvisioningServerErrorException
+ {
+ Log.v(TAG, "prepareDrm: uuid: " + uuid + " mOnDrmConfigHelper: " + mOnDrmConfigHelper);
+
+ boolean allDoneWithoutProvisioning = false;
+
+ synchronized (mDrmLock) {
+
+ // only allowing if tied to a protected source; might relax for releasing offline keys
+ if (mDrmInfoImpl == null) {
+ final String msg = "prepareDrm(): Wrong usage: The player must be prepared and " +
+ "DRM info be retrieved before this call.";
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mActiveDrmScheme) {
+ final String msg = "prepareDrm(): Wrong usage: There is already " +
+ "an active DRM scheme with " + mDrmUUID;
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mPrepareDrmInProgress) {
+ final String msg = "prepareDrm(): Wrong usage: There is already " +
+ "a pending prepareDrm call.";
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ if (mDrmProvisioningInProgress) {
+ final String msg = "prepareDrm(): Unexpectd: Provisioning is already in progress.";
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+
+ // shouldn't need this; just for safeguard
+ cleanDrmObj();
+
+ mPrepareDrmInProgress = true;
+
+ try {
+ // only creating the DRM object to allow pre-openSession configuration
+ prepareDrm_createDrmStep(uuid);
+ } catch (Exception e) {
+ Log.w(TAG, "prepareDrm(): Exception ", e);
+ mPrepareDrmInProgress = false;
+ throw e;
+ }
+
+ mDrmConfigAllowed = true;
+ } // synchronized
+
+
+ // call the callback outside the lock
+ if (mOnDrmConfigHelper != null) {
+ mOnDrmConfigHelper.onDrmConfig(this);
+ }
+
+ synchronized (mDrmLock) {
+ mDrmConfigAllowed = false;
+ boolean earlyExit = false;
+
+ try {
+ prepareDrm_openSessionStep(uuid);
+
+ mDrmUUID = uuid;
+ mActiveDrmScheme = true;
+
+ allDoneWithoutProvisioning = true;
+ } catch (IllegalStateException e) {
+ final String msg = "prepareDrm(): Wrong usage: The player must be " +
+ "in the prepared state to call prepareDrm().";
+ Log.e(TAG, msg);
+ earlyExit = true;
+ throw new IllegalStateException(msg);
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, "prepareDrm: NotProvisionedException");
+
+ // handle provisioning internally; it'll reset mPrepareDrmInProgress
+ int result = HandleProvisioninig(uuid);
+
+ // if blocking mode, we're already done;
+ // if non-blocking mode, we attempted to launch background provisioning
+ if (result != PREPARE_DRM_STATUS_SUCCESS) {
+ earlyExit = true;
+ String msg;
+
+ switch (result) {
+ case PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR:
+ msg = "prepareDrm: Provisioning was required but failed " +
+ "due to a network error.";
+ Log.e(TAG, msg);
+ throw new ProvisioningNetworkErrorExceptionImpl(msg);
+
+ case PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR:
+ msg = "prepareDrm: Provisioning was required but the request " +
+ "was denied by the server.";
+ Log.e(TAG, msg);
+ throw new ProvisioningServerErrorExceptionImpl(msg);
+
+ case PREPARE_DRM_STATUS_PREPARATION_ERROR:
+ default: // default for safeguard
+ msg = "prepareDrm: Post-provisioning preparation failed.";
+ Log.e(TAG, msg);
+ throw new IllegalStateException(msg);
+ }
+ }
+ // nothing else to do;
+ // if blocking or non-blocking, HandleProvisioninig does the re-attempt & cleanup
+ } catch (Exception e) {
+ Log.e(TAG, "prepareDrm: Exception " + e);
+ earlyExit = true;
+ throw e;
+ } finally {
+ if (!mDrmProvisioningInProgress) {// if early exit other than provisioning exception
+ mPrepareDrmInProgress = false;
+ }
+ if (earlyExit) { // cleaning up object if didn't succeed
+ cleanDrmObj();
+ }
+ } // finally
+ } // synchronized
+
+
+ // if finished successfully without provisioning, call the callback outside the lock
+ if (allDoneWithoutProvisioning) {
+ final Executor drmEventExec;
+ final DrmEventCallback drmEventCb;
+ synchronized (mDrmEventCbLock) {
+ drmEventExec = mDrmEventExec;
+ drmEventCb = mDrmEventCb;
+ }
+ if (drmEventExec != null && drmEventCb != null) {
+ drmEventExec.execute(() -> drmEventCb.onDrmPrepared(
+ this, PREPARE_DRM_STATUS_SUCCESS));
+ }
+ }
+
+ }
+
+
+ private native void _releaseDrm();
+
+ /**
+ * Releases the DRM session
+ * <p>
+ * The player has to have an active DRM session and be in stopped, or prepared
+ * state before this call is made.
+ * A {@code reset()} call will release the DRM session implicitly.
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session to release
+ */
+ @Override
+ public void releaseDrm()
+ throws NoDrmSchemeException
+ {
+ Log.v(TAG, "releaseDrm:");
+
+ synchronized (mDrmLock) {
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, "releaseDrm(): No active DRM scheme to release.");
+ throw new NoDrmSchemeExceptionImpl("releaseDrm: No active DRM scheme to release.");
+ }
+
+ try {
+ // we don't have the player's state in this layer. The below call raises
+ // exception if we're in a non-stopped/prepared state.
+
+ // for cleaning native/mediaserver crypto object
+ _releaseDrm();
+
+ // for cleaning client-side MediaDrm object; only called if above has succeeded
+ cleanDrmObj();
+
+ mActiveDrmScheme = false;
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "releaseDrm: Exception ", e);
+ throw new IllegalStateException("releaseDrm: The player is not in a valid state.");
+ } catch (Exception e) {
+ Log.e(TAG, "releaseDrm: Exception ", e);
+ }
+ } // synchronized
+ }
+
+
+ /**
+ * A key request/response exchange occurs between the app and a license server
+ * to obtain or release keys used to decrypt encrypted content.
+ * <p>
+ * getKeyRequest() is used to obtain an opaque key request byte array that is
+ * delivered to the license server. The opaque key request byte array is returned
+ * in KeyRequest.data. The recommended URL to deliver the key request to is
+ * returned in KeyRequest.defaultUrl.
+ * <p>
+ * After the app has received the key request response from the server,
+ * it should deliver to the response to the DRM engine plugin using the method
+ * {@link #provideKeyResponse}.
+ *
+ * @param keySetId is the key-set identifier of the offline keys being released when keyType is
+ * {@link MediaDrm#KEY_TYPE_RELEASE}. It should be set to null for other key requests, when
+ * keyType is {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}.
+ *
+ * @param initData is the container-specific initialization data when the keyType is
+ * {@link MediaDrm#KEY_TYPE_STREAMING} or {@link MediaDrm#KEY_TYPE_OFFLINE}. Its meaning is
+ * interpreted based on the mime type provided in the mimeType parameter. It could
+ * contain, for example, the content ID, key ID or other data obtained from the content
+ * metadata that is required in generating the key request.
+ * When the keyType is {@link MediaDrm#KEY_TYPE_RELEASE}, it should be set to null.
+ *
+ * @param mimeType identifies the mime type of the content
+ *
+ * @param keyType specifies the type of the request. The request may be to acquire
+ * keys for streaming, {@link MediaDrm#KEY_TYPE_STREAMING}, or for offline content
+ * {@link MediaDrm#KEY_TYPE_OFFLINE}, or to release previously acquired
+ * keys ({@link MediaDrm#KEY_TYPE_RELEASE}), which are identified by a keySetId.
+ *
+ * @param optionalParameters are included in the key request message to
+ * allow a client application to provide additional message parameters to the server.
+ * This may be {@code null} if no additional parameters are to be sent.
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session
+ */
+ @Override
+ @NonNull
+ public MediaDrm.KeyRequest getKeyRequest(@Nullable byte[] keySetId, @Nullable byte[] initData,
+ @Nullable String mimeType, @MediaDrm.KeyType int keyType,
+ @Nullable Map<String, String> optionalParameters)
+ throws NoDrmSchemeException
+ {
+ Log.v(TAG, "getKeyRequest: " +
+ " keySetId: " + keySetId + " initData:" + initData + " mimeType: " + mimeType +
+ " keyType: " + keyType + " optionalParameters: " + optionalParameters);
+
+ synchronized (mDrmLock) {
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, "getKeyRequest NoDrmSchemeException");
+ throw new NoDrmSchemeExceptionImpl("getKeyRequest: Has to set a DRM scheme first.");
+ }
+
+ try {
+ byte[] scope = (keyType != MediaDrm.KEY_TYPE_RELEASE) ?
+ mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+ keySetId; // keySetId for KEY_TYPE_RELEASE
+
+ HashMap<String, String> hmapOptionalParameters =
+ (optionalParameters != null) ?
+ new HashMap<String, String>(optionalParameters) :
+ null;
+
+ MediaDrm.KeyRequest request = mDrmObj.getKeyRequest(scope, initData, mimeType,
+ keyType, hmapOptionalParameters);
+ Log.v(TAG, "getKeyRequest: --> request: " + request);
+
+ return request;
+
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, "getKeyRequest NotProvisionedException: " +
+ "Unexpected. Shouldn't have reached here.");
+ throw new IllegalStateException("getKeyRequest: Unexpected provisioning error.");
+ } catch (Exception e) {
+ Log.w(TAG, "getKeyRequest Exception " + e);
+ throw e;
+ }
+
+ } // synchronized
+ }
+
+
+ /**
+ * A key response is received from the license server by the app, then it is
+ * provided to the DRM engine plugin using provideKeyResponse. When the
+ * response is for an offline key request, a key-set identifier is returned that
+ * can be used to later restore the keys to a new session with the method
+ * {@ link # restoreKeys}.
+ * When the response is for a streaming or release request, null is returned.
+ *
+ * @param keySetId When the response is for a release request, keySetId identifies
+ * the saved key associated with the release request (i.e., the same keySetId
+ * passed to the earlier {@ link # getKeyRequest} call. It MUST be null when the
+ * response is for either streaming or offline key requests.
+ *
+ * @param response the byte array response from the server
+ *
+ * @throws NoDrmSchemeException if there is no active DRM session
+ * @throws DeniedByServerException if the response indicates that the
+ * server rejected the request
+ */
+ @Override
+ public byte[] provideKeyResponse(@Nullable byte[] keySetId, @NonNull byte[] response)
+ throws NoDrmSchemeException, DeniedByServerException
+ {
+ Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response);
+
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme) {
+ Log.e(TAG, "getKeyRequest NoDrmSchemeException");
+ throw new NoDrmSchemeExceptionImpl("getKeyRequest: Has to set a DRM scheme first.");
+ }
+
+ try {
+ byte[] scope = (keySetId == null) ?
+ mDrmSessionId : // sessionId for KEY_TYPE_STREAMING/OFFLINE
+ keySetId; // keySetId for KEY_TYPE_RELEASE
+
+ byte[] keySetResult = mDrmObj.provideKeyResponse(scope, response);
+
+ Log.v(TAG, "provideKeyResponse: keySetId: " + keySetId + " response: " + response +
+ " --> " + keySetResult);
+
+
+ return keySetResult;
+
+ } catch (NotProvisionedException e) {
+ Log.w(TAG, "provideKeyResponse NotProvisionedException: " +
+ "Unexpected. Shouldn't have reached here.");
+ throw new IllegalStateException("provideKeyResponse: " +
+ "Unexpected provisioning error.");
+ } catch (Exception e) {
+ Log.w(TAG, "provideKeyResponse Exception " + e);
+ throw e;
+ }
+ } // synchronized
+ }
+
+
+ /**
+ * Restore persisted offline keys into a new session. keySetId identifies the
+ * keys to load, obtained from a prior call to {@link #provideKeyResponse}.
+ *
+ * @param keySetId identifies the saved key set to restore
+ */
+ @Override
+ public void restoreKeys(@NonNull byte[] keySetId)
+ throws NoDrmSchemeException
+ {
+ Log.v(TAG, "restoreKeys: keySetId: " + keySetId);
+
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme) {
+ Log.w(TAG, "restoreKeys NoDrmSchemeException");
+ throw new NoDrmSchemeExceptionImpl("restoreKeys: Has to set a DRM scheme first.");
+ }
+
+ try {
+ mDrmObj.restoreKeys(mDrmSessionId, keySetId);
+ } catch (Exception e) {
+ Log.w(TAG, "restoreKeys Exception " + e);
+ throw e;
+ }
+
+ } // synchronized
+ }
+
+
+ /**
+ * Read a DRM engine plugin String property value, given the property name string.
+ * <p>
+ * @param propertyName the property name
+ *
+ * Standard fields names are:
+ * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
+ * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
+ */
+ @Override
+ @NonNull
+ public String getDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName)
+ throws NoDrmSchemeException
+ {
+ Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName);
+
+ String value;
+ synchronized (mDrmLock) {
+
+ if (!mActiveDrmScheme && !mDrmConfigAllowed) {
+ Log.w(TAG, "getDrmPropertyString NoDrmSchemeException");
+ throw new NoDrmSchemeExceptionImpl("getDrmPropertyString: Has to prepareDrm() first.");
+ }
+
+ try {
+ value = mDrmObj.getPropertyString(propertyName);
+ } catch (Exception e) {
+ Log.w(TAG, "getDrmPropertyString Exception " + e);
+ throw e;
+ }
+ } // synchronized
+
+ Log.v(TAG, "getDrmPropertyString: propertyName: " + propertyName + " --> value: " + value);
+
+ return value;
+ }
+
+
+ /**
+ * Set a DRM engine plugin String property value.
+ * <p>
+ * @param propertyName the property name
+ * @param value the property value
+ *
+ * Standard fields names are:
+ * {@link MediaDrm#PROPERTY_VENDOR}, {@link MediaDrm#PROPERTY_VERSION},
+ * {@link MediaDrm#PROPERTY_DESCRIPTION}, {@link MediaDrm#PROPERTY_ALGORITHMS}
+ */
+ @Override
+ public void setDrmPropertyString(@NonNull @MediaDrm.StringProperty String propertyName,
+ @NonNull String value)
+ throws NoDrmSchemeException
+ {
+ Log.v(TAG, "setDrmPropertyString: propertyName: " + propertyName + " value: " + value);
+
+ synchronized (mDrmLock) {
+
+ if ( !mActiveDrmScheme && !mDrmConfigAllowed ) {
+ Log.w(TAG, "setDrmPropertyString NoDrmSchemeException");
+ throw new NoDrmSchemeExceptionImpl("setDrmPropertyString: Has to prepareDrm() first.");
+ }
+
+ try {
+ mDrmObj.setPropertyString(propertyName, value);
+ } catch ( Exception e ) {
+ Log.w(TAG, "setDrmPropertyString Exception " + e);
+ throw e;
+ }
+ } // synchronized
+ }
+
+ /**
+ * Encapsulates the DRM properties of the source.
+ */
+ public static final class DrmInfoImpl extends DrmInfo {
+ private Map<UUID, byte[]> mapPssh;
+ private UUID[] supportedSchemes;
+
+ /**
+ * Returns the PSSH info of the data source for each supported DRM scheme.
+ */
+ @Override
+ public Map<UUID, byte[]> getPssh() {
+ return mapPssh;
+ }
+
+ /**
+ * Returns the intersection of the data source and the device DRM schemes.
+ * It effectively identifies the subset of the source's DRM schemes which
+ * are supported by the device too.
+ */
+ @Override
+ public List<UUID> getSupportedSchemes() {
+ return Arrays.asList(supportedSchemes);
+ }
+
+ private DrmInfoImpl(Map<UUID, byte[]> Pssh, UUID[] SupportedSchemes) {
+ mapPssh = Pssh;
+ supportedSchemes = SupportedSchemes;
+ }
+
+ private DrmInfoImpl(Parcel parcel) {
+ Log.v(TAG, "DrmInfoImpl(" + parcel + ") size " + parcel.dataSize());
+
+ int psshsize = parcel.readInt();
+ byte[] pssh = new byte[psshsize];
+ parcel.readByteArray(pssh);
+
+ Log.v(TAG, "DrmInfoImpl() PSSH: " + arrToHex(pssh));
+ mapPssh = parsePSSH(pssh, psshsize);
+ Log.v(TAG, "DrmInfoImpl() PSSH: " + mapPssh);
+
+ int supportedDRMsCount = parcel.readInt();
+ supportedSchemes = new UUID[supportedDRMsCount];
+ for (int i = 0; i < supportedDRMsCount; i++) {
+ byte[] uuid = new byte[16];
+ parcel.readByteArray(uuid);
+
+ supportedSchemes[i] = bytesToUUID(uuid);
+
+ Log.v(TAG, "DrmInfoImpl() supportedScheme[" + i + "]: " +
+ supportedSchemes[i]);
+ }
+
+ Log.v(TAG, "DrmInfoImpl() Parcel psshsize: " + psshsize +
+ " supportedDRMsCount: " + supportedDRMsCount);
+ }
+
+ private DrmInfoImpl makeCopy() {
+ return new DrmInfoImpl(this.mapPssh, this.supportedSchemes);
+ }
+
+ private String arrToHex(byte[] bytes) {
+ String out = "0x";
+ for (int i = 0; i < bytes.length; i++) {
+ out += String.format("%02x", bytes[i]);
+ }
+
+ return out;
+ }
+
+ private UUID bytesToUUID(byte[] uuid) {
+ long msb = 0, lsb = 0;
+ for (int i = 0; i < 8; i++) {
+ msb |= ( ((long)uuid[i] & 0xff) << (8 * (7 - i)) );
+ lsb |= ( ((long)uuid[i+8] & 0xff) << (8 * (7 - i)) );
+ }
+
+ return new UUID(msb, lsb);
+ }
+
+ private Map<UUID, byte[]> parsePSSH(byte[] pssh, int psshsize) {
+ Map<UUID, byte[]> result = new HashMap<UUID, byte[]>();
+
+ final int UUID_SIZE = 16;
+ final int DATALEN_SIZE = 4;
+
+ int len = psshsize;
+ int numentries = 0;
+ int i = 0;
+
+ while (len > 0) {
+ if (len < UUID_SIZE) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
+ "UUID: (%d < 16) pssh: %d", len, psshsize));
+ return null;
+ }
+
+ byte[] subset = Arrays.copyOfRange(pssh, i, i + UUID_SIZE);
+ UUID uuid = bytesToUUID(subset);
+ i += UUID_SIZE;
+ len -= UUID_SIZE;
+
+ // get data length
+ if (len < 4) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
+ "datalen: (%d < 4) pssh: %d", len, psshsize));
+ return null;
+ }
+
+ subset = Arrays.copyOfRange(pssh, i, i+DATALEN_SIZE);
+ int datalen = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) ?
+ ((subset[3] & 0xff) << 24) | ((subset[2] & 0xff) << 16) |
+ ((subset[1] & 0xff) << 8) | (subset[0] & 0xff) :
+ ((subset[0] & 0xff) << 24) | ((subset[1] & 0xff) << 16) |
+ ((subset[2] & 0xff) << 8) | (subset[3] & 0xff) ;
+ i += DATALEN_SIZE;
+ len -= DATALEN_SIZE;
+
+ if (len < datalen) {
+ Log.w(TAG, String.format("parsePSSH: len is too short to parse " +
+ "data: (%d < %d) pssh: %d", len, datalen, psshsize));
+ return null;
+ }
+
+ byte[] data = Arrays.copyOfRange(pssh, i, i+datalen);
+
+ // skip the data
+ i += datalen;
+ len -= datalen;
+
+ Log.v(TAG, String.format("parsePSSH[%d]: <%s, %s> pssh: %d",
+ numentries, uuid, arrToHex(data), psshsize));
+ numentries++;
+ result.put(uuid, data);
+ }
+
+ return result;
+ }
+
+ }; // DrmInfoImpl
+
+ /**
+ * Thrown when a DRM method is called before preparing a DRM scheme through prepareDrm().
+ * Extends MediaDrm.MediaDrmException
+ */
+ public static final class NoDrmSchemeExceptionImpl extends NoDrmSchemeException {
+ public NoDrmSchemeExceptionImpl(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Thrown when the device requires DRM provisioning but the provisioning attempt has
+ * failed due to a network error (Internet reachability, timeout, etc.).
+ * Extends MediaDrm.MediaDrmException
+ */
+ public static final class ProvisioningNetworkErrorExceptionImpl
+ extends ProvisioningNetworkErrorException {
+ public ProvisioningNetworkErrorExceptionImpl(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+ /**
+ * Thrown when the device requires DRM provisioning but the provisioning attempt has
+ * failed due to the provisioning server denying the request.
+ * Extends MediaDrm.MediaDrmException
+ */
+ public static final class ProvisioningServerErrorExceptionImpl
+ extends ProvisioningServerErrorException {
+ public ProvisioningServerErrorExceptionImpl(String detailMessage) {
+ super(detailMessage);
+ }
+ }
+
+
+ private native void _prepareDrm(@NonNull byte[] uuid, @NonNull byte[] drmSessionId);
+
+ // Modular DRM helpers
+
+ private void prepareDrm_createDrmStep(@NonNull UUID uuid)
+ throws UnsupportedSchemeException {
+ Log.v(TAG, "prepareDrm_createDrmStep: UUID: " + uuid);
+
+ try {
+ mDrmObj = new MediaDrm(uuid);
+ Log.v(TAG, "prepareDrm_createDrmStep: Created mDrmObj=" + mDrmObj);
+ } catch (Exception e) { // UnsupportedSchemeException
+ Log.e(TAG, "prepareDrm_createDrmStep: MediaDrm failed with " + e);
+ throw e;
+ }
+ }
+
+ private void prepareDrm_openSessionStep(@NonNull UUID uuid)
+ throws NotProvisionedException, ResourceBusyException {
+ Log.v(TAG, "prepareDrm_openSessionStep: uuid: " + uuid);
+
+ // TODO: don't need an open session for a future specialKeyReleaseDrm mode but we should do
+ // it anyway so it raises provisioning error if needed. We'd rather handle provisioning
+ // at prepareDrm/openSession rather than getKeyRequest/provideKeyResponse
+ try {
+ mDrmSessionId = mDrmObj.openSession();
+ Log.v(TAG, "prepareDrm_openSessionStep: mDrmSessionId=" + mDrmSessionId);
+
+ // Sending it down to native/mediaserver to create the crypto object
+ // This call could simply fail due to bad player state, e.g., after play().
+ _prepareDrm(getByteArrayFromUUID(uuid), mDrmSessionId);
+ Log.v(TAG, "prepareDrm_openSessionStep: _prepareDrm/Crypto succeeded");
+
+ } catch (Exception e) { //ResourceBusyException, NotProvisionedException
+ Log.e(TAG, "prepareDrm_openSessionStep: open/crypto failed with " + e);
+ throw e;
+ }
+
+ }
+
+ private class ProvisioningThread extends Thread {
+ public static final int TIMEOUT_MS = 60000;
+
+ private UUID uuid;
+ private String urlStr;
+ private Object drmLock;
+ private MediaPlayer2Impl mediaPlayer;
+ private int status;
+ private boolean finished;
+ public int status() {
+ return status;
+ }
+
+ public ProvisioningThread initialize(MediaDrm.ProvisionRequest request,
+ UUID uuid, MediaPlayer2Impl mediaPlayer) {
+ // lock is held by the caller
+ drmLock = mediaPlayer.mDrmLock;
+ this.mediaPlayer = mediaPlayer;
+
+ urlStr = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
+ this.uuid = uuid;
+
+ status = PREPARE_DRM_STATUS_PREPARATION_ERROR;
+
+ Log.v(TAG, "HandleProvisioninig: Thread is initialised url: " + urlStr);
+ return this;
+ }
+
+ public void run() {
+
+ byte[] response = null;
+ boolean provisioningSucceeded = false;
+ try {
+ URL url = new URL(urlStr);
+ final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ try {
+ connection.setRequestMethod("POST");
+ connection.setDoOutput(false);
+ connection.setDoInput(true);
+ connection.setConnectTimeout(TIMEOUT_MS);
+ connection.setReadTimeout(TIMEOUT_MS);
+
+ connection.connect();
+ response = Streams.readFully(connection.getInputStream());
+
+ Log.v(TAG, "HandleProvisioninig: Thread run: response " +
+ response.length + " " + response);
+ } catch (Exception e) {
+ status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
+ Log.w(TAG, "HandleProvisioninig: Thread run: connect " + e + " url: " + url);
+ } finally {
+ connection.disconnect();
+ }
+ } catch (Exception e) {
+ status = PREPARE_DRM_STATUS_PROVISIONING_NETWORK_ERROR;
+ Log.w(TAG, "HandleProvisioninig: Thread run: openConnection " + e);
+ }
+
+ if (response != null) {
+ try {
+ mDrmObj.provideProvisionResponse(response);
+ Log.v(TAG, "HandleProvisioninig: Thread run: " +
+ "provideProvisionResponse SUCCEEDED!");
+
+ provisioningSucceeded = true;
+ } catch (Exception e) {
+ status = PREPARE_DRM_STATUS_PROVISIONING_SERVER_ERROR;
+ Log.w(TAG, "HandleProvisioninig: Thread run: " +
+ "provideProvisionResponse " + e);
+ }
+ }
+
+ boolean succeeded = false;
+
+ final Executor drmEventExec;
+ final DrmEventCallback drmEventCb;
+ synchronized (mDrmEventCbLock) {
+ drmEventExec = mDrmEventExec;
+ drmEventCb = mDrmEventCb;
+ }
+ // non-blocking mode needs the lock
+ if (drmEventExec != null && drmEventCb != null) {
+
+ synchronized (drmLock) {
+ // continuing with prepareDrm
+ if (provisioningSucceeded) {
+ succeeded = mediaPlayer.resumePrepareDrm(uuid);
+ status = (succeeded) ?
+ PREPARE_DRM_STATUS_SUCCESS :
+ PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ }
+ mediaPlayer.mDrmProvisioningInProgress = false;
+ mediaPlayer.mPrepareDrmInProgress = false;
+ if (!succeeded) {
+ cleanDrmObj(); // cleaning up if it hasn't gone through while in the lock
+ }
+ } // synchronized
+
+ // calling the callback outside the lock
+ drmEventExec.execute(() -> drmEventCb.onDrmPrepared(mediaPlayer, status));
+ } else { // blocking mode already has the lock
+
+ // continuing with prepareDrm
+ if (provisioningSucceeded) {
+ succeeded = mediaPlayer.resumePrepareDrm(uuid);
+ status = (succeeded) ?
+ PREPARE_DRM_STATUS_SUCCESS :
+ PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ }
+ mediaPlayer.mDrmProvisioningInProgress = false;
+ mediaPlayer.mPrepareDrmInProgress = false;
+ if (!succeeded) {
+ cleanDrmObj(); // cleaning up if it hasn't gone through
+ }
+ }
+
+ finished = true;
+ } // run()
+
+ } // ProvisioningThread
+
+ private int HandleProvisioninig(UUID uuid) {
+ // the lock is already held by the caller
+
+ if (mDrmProvisioningInProgress) {
+ Log.e(TAG, "HandleProvisioninig: Unexpected mDrmProvisioningInProgress");
+ return PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ }
+
+ MediaDrm.ProvisionRequest provReq = mDrmObj.getProvisionRequest();
+ if (provReq == null) {
+ Log.e(TAG, "HandleProvisioninig: getProvisionRequest returned null.");
+ return PREPARE_DRM_STATUS_PREPARATION_ERROR;
+ }
+
+ Log.v(TAG, "HandleProvisioninig provReq " +
+ " data: " + provReq.getData() + " url: " + provReq.getDefaultUrl());
+
+ // networking in a background thread
+ mDrmProvisioningInProgress = true;
+
+ mDrmProvisioningThread = new ProvisioningThread().initialize(provReq, uuid, this);
+ mDrmProvisioningThread.start();
+
+ int result;
+
+ // non-blocking: this is not the final result
+ final Executor drmEventExec;
+ final DrmEventCallback drmEventCb;
+ synchronized (mDrmEventCbLock) {
+ drmEventExec = mDrmEventExec;
+ drmEventCb = mDrmEventCb;
+ }
+ if (drmEventCb != null && drmEventExec != null) {
+ result = PREPARE_DRM_STATUS_SUCCESS;
+ } else {
+ // if blocking mode, wait till provisioning is done
+ try {
+ mDrmProvisioningThread.join();
+ } catch (Exception e) {
+ Log.w(TAG, "HandleProvisioninig: Thread.join Exception " + e);
+ }
+ result = mDrmProvisioningThread.status();
+ // no longer need the thread
+ mDrmProvisioningThread = null;
+ }
+
+ return result;
+ }
+
+ private boolean resumePrepareDrm(UUID uuid) {
+ Log.v(TAG, "resumePrepareDrm: uuid: " + uuid);
+
+ // mDrmLock is guaranteed to be held
+ boolean success = false;
+ try {
+ // resuming
+ prepareDrm_openSessionStep(uuid);
+
+ mDrmUUID = uuid;
+ mActiveDrmScheme = true;
+
+ success = true;
+ } catch (Exception e) {
+ Log.w(TAG, "HandleProvisioninig: Thread run _prepareDrm resume failed with " + e);
+ // mDrmObj clean up is done by the caller
+ }
+
+ return success;
+ }
+
+ private void resetDrmState() {
+ synchronized (mDrmLock) {
+ Log.v(TAG, "resetDrmState: " +
+ " mDrmInfoImpl=" + mDrmInfoImpl +
+ " mDrmProvisioningThread=" + mDrmProvisioningThread +
+ " mPrepareDrmInProgress=" + mPrepareDrmInProgress +
+ " mActiveDrmScheme=" + mActiveDrmScheme);
+
+ mDrmInfoResolved = false;
+ mDrmInfoImpl = null;
+
+ if (mDrmProvisioningThread != null) {
+ // timeout; relying on HttpUrlConnection
+ try {
+ mDrmProvisioningThread.join();
+ }
+ catch (InterruptedException e) {
+ Log.w(TAG, "resetDrmState: ProvThread.join Exception " + e);
+ }
+ mDrmProvisioningThread = null;
+ }
+
+ mPrepareDrmInProgress = false;
+ mActiveDrmScheme = false;
+
+ cleanDrmObj();
+ } // synchronized
+ }
+
+ private void cleanDrmObj() {
+ // the caller holds mDrmLock
+ Log.v(TAG, "cleanDrmObj: mDrmObj=" + mDrmObj + " mDrmSessionId=" + mDrmSessionId);
+
+ if (mDrmSessionId != null) {
+ mDrmObj.closeSession(mDrmSessionId);
+ mDrmSessionId = null;
+ }
+ if (mDrmObj != null) {
+ mDrmObj.release();
+ mDrmObj = null;
+ }
+ }
+
+ private static final byte[] getByteArrayFromUUID(@NonNull UUID uuid) {
+ long msb = uuid.getMostSignificantBits();
+ long lsb = uuid.getLeastSignificantBits();
+
+ byte[] uuidBytes = new byte[16];
+ for (int i = 0; i < 8; ++i) {
+ uuidBytes[i] = (byte)(msb >>> (8 * (7 - i)));
+ uuidBytes[8 + i] = (byte)(lsb >>> (8 * (7 - i)));
+ }
+
+ return uuidBytes;
+ }
+
+ // Modular DRM end
+
+ /*
+ * Test whether a given video scaling mode is supported.
+ */
+ private boolean isVideoScalingModeSupported(int mode) {
+ return (mode == VIDEO_SCALING_MODE_SCALE_TO_FIT ||
+ mode == VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
+ }
+
+ /** @hide */
+ static class TimeProvider implements MediaTimeProvider {
+ private static final String TAG = "MTP";
+ private static final long MAX_NS_WITHOUT_POSITION_CHECK = 5000000000L;
+ private static final long MAX_EARLY_CALLBACK_US = 1000;
+ private static final long TIME_ADJUSTMENT_RATE = 2; /* meaning 1/2 */
+ private long mLastTimeUs = 0;
+ private MediaPlayer2Impl mPlayer;
+ private boolean mPaused = true;
+ private boolean mStopped = true;
+ private boolean mBuffering;
+ private long mLastReportedTime;
+ // since we are expecting only a handful listeners per stream, there is
+ // no need for log(N) search performance
+ private MediaTimeProvider.OnMediaTimeListener mListeners[];
+ private long mTimes[];
+ private Handler mEventHandler;
+ private boolean mRefresh = false;
+ private boolean mPausing = false;
+ private boolean mSeeking = false;
+ private static final int NOTIFY = 1;
+ private static final int NOTIFY_TIME = 0;
+ private static final int NOTIFY_STOP = 2;
+ private static final int NOTIFY_SEEK = 3;
+ private static final int NOTIFY_TRACK_DATA = 4;
+ private HandlerThread mHandlerThread;
+
+ /** @hide */
+ public boolean DEBUG = false;
+
+ public TimeProvider(MediaPlayer2Impl mp) {
+ mPlayer = mp;
+ try {
+ getCurrentTimeUs(true, false);
+ } catch (IllegalStateException e) {
+ // we assume starting position
+ mRefresh = true;
+ }
+
+ Looper looper;
+ if ((looper = Looper.myLooper()) == null &&
+ (looper = Looper.getMainLooper()) == null) {
+ // Create our own looper here in case MP was created without one
+ mHandlerThread = new HandlerThread("MediaPlayer2MTPEventThread",
+ Process.THREAD_PRIORITY_FOREGROUND);
+ mHandlerThread.start();
+ looper = mHandlerThread.getLooper();
+ }
+ mEventHandler = new EventHandler(looper);
+
+ mListeners = new MediaTimeProvider.OnMediaTimeListener[0];
+ mTimes = new long[0];
+ mLastTimeUs = 0;
+ }
+
+ private void scheduleNotification(int type, long delayUs) {
+ // ignore time notifications until seek is handled
+ if (mSeeking && type == NOTIFY_TIME) {
+ return;
+ }
+
+ if (DEBUG) Log.v(TAG, "scheduleNotification " + type + " in " + delayUs);
+ mEventHandler.removeMessages(NOTIFY);
+ Message msg = mEventHandler.obtainMessage(NOTIFY, type, 0);
+ mEventHandler.sendMessageDelayed(msg, (int) (delayUs / 1000));
+ }
+
+ /** @hide */
+ public void close() {
+ mEventHandler.removeMessages(NOTIFY);
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ mHandlerThread = null;
+ }
+ }
+
+ /** @hide */
+ protected void finalize() {
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ }
+ }
+
+ /** @hide */
+ public void onNotifyTime() {
+ synchronized (this) {
+ if (DEBUG) Log.d(TAG, "onNotifyTime: ");
+ scheduleNotification(NOTIFY_TIME, 0 /* delay */);
+ }
+ }
+
+ /** @hide */
+ public void onPaused(boolean paused) {
+ synchronized(this) {
+ if (DEBUG) Log.d(TAG, "onPaused: " + paused);
+ if (mStopped) { // handle as seek if we were stopped
+ mStopped = false;
+ mSeeking = true;
+ scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
+ } else {
+ mPausing = paused; // special handling if player disappeared
+ mSeeking = false;
+ scheduleNotification(NOTIFY_TIME, 0 /* delay */);
+ }
+ }
+ }
+
+ /** @hide */
+ public void onBuffering(boolean buffering) {
+ synchronized (this) {
+ if (DEBUG) Log.d(TAG, "onBuffering: " + buffering);
+ mBuffering = buffering;
+ scheduleNotification(NOTIFY_TIME, 0 /* delay */);
+ }
+ }
+
+ /** @hide */
+ public void onStopped() {
+ synchronized(this) {
+ if (DEBUG) Log.d(TAG, "onStopped");
+ mPaused = true;
+ mStopped = true;
+ mSeeking = false;
+ mBuffering = false;
+ scheduleNotification(NOTIFY_STOP, 0 /* delay */);
+ }
+ }
+
+ /** @hide */
+ public void onSeekComplete(MediaPlayer2Impl mp) {
+ synchronized(this) {
+ mStopped = false;
+ mSeeking = true;
+ scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
+ }
+ }
+
+ /** @hide */
+ public void onNewPlayer() {
+ if (mRefresh) {
+ synchronized(this) {
+ mStopped = false;
+ mSeeking = true;
+ mBuffering = false;
+ scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
+ }
+ }
+ }
+
+ private synchronized void notifySeek() {
+ mSeeking = false;
+ try {
+ long timeUs = getCurrentTimeUs(true, false);
+ if (DEBUG) Log.d(TAG, "onSeekComplete at " + timeUs);
+
+ for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) {
+ if (listener == null) {
+ break;
+ }
+ listener.onSeek(timeUs);
+ }
+ } catch (IllegalStateException e) {
+ // we should not be there, but at least signal pause
+ if (DEBUG) Log.d(TAG, "onSeekComplete but no player");
+ mPausing = true; // special handling if player disappeared
+ notifyTimedEvent(false /* refreshTime */);
+ }
+ }
+
+ private synchronized void notifyTrackData(Pair<SubtitleTrack, byte[]> trackData) {
+ SubtitleTrack track = trackData.first;
+ byte[] data = trackData.second;
+ track.onData(data, true /* eos */, ~0 /* runID: keep forever */);
+ }
+
+ private synchronized void notifyStop() {
+ for (MediaTimeProvider.OnMediaTimeListener listener: mListeners) {
+ if (listener == null) {
+ break;
+ }
+ listener.onStop();
+ }
+ }
+
+ private int registerListener(MediaTimeProvider.OnMediaTimeListener listener) {
+ int i = 0;
+ for (; i < mListeners.length; i++) {
+ if (mListeners[i] == listener || mListeners[i] == null) {
+ break;
+ }
+ }
+
+ // new listener
+ if (i >= mListeners.length) {
+ MediaTimeProvider.OnMediaTimeListener[] newListeners =
+ new MediaTimeProvider.OnMediaTimeListener[i + 1];
+ long[] newTimes = new long[i + 1];
+ System.arraycopy(mListeners, 0, newListeners, 0, mListeners.length);
+ System.arraycopy(mTimes, 0, newTimes, 0, mTimes.length);
+ mListeners = newListeners;
+ mTimes = newTimes;
+ }
+
+ if (mListeners[i] == null) {
+ mListeners[i] = listener;
+ mTimes[i] = MediaTimeProvider.NO_TIME;
+ }
+ return i;
+ }
+
+ public void notifyAt(
+ long timeUs, MediaTimeProvider.OnMediaTimeListener listener) {
+ synchronized(this) {
+ if (DEBUG) Log.d(TAG, "notifyAt " + timeUs);
+ mTimes[registerListener(listener)] = timeUs;
+ scheduleNotification(NOTIFY_TIME, 0 /* delay */);
+ }
+ }
+
+ public void scheduleUpdate(MediaTimeProvider.OnMediaTimeListener listener) {
+ synchronized(this) {
+ if (DEBUG) Log.d(TAG, "scheduleUpdate");
+ int i = registerListener(listener);
+
+ if (!mStopped) {
+ mTimes[i] = 0;
+ scheduleNotification(NOTIFY_TIME, 0 /* delay */);
+ }
+ }
+ }
+
+ public void cancelNotifications(
+ MediaTimeProvider.OnMediaTimeListener listener) {
+ synchronized(this) {
+ int i = 0;
+ for (; i < mListeners.length; i++) {
+ if (mListeners[i] == listener) {
+ System.arraycopy(mListeners, i + 1,
+ mListeners, i, mListeners.length - i - 1);
+ System.arraycopy(mTimes, i + 1,
+ mTimes, i, mTimes.length - i - 1);
+ mListeners[mListeners.length - 1] = null;
+ mTimes[mTimes.length - 1] = NO_TIME;
+ break;
+ } else if (mListeners[i] == null) {
+ break;
+ }
+ }
+
+ scheduleNotification(NOTIFY_TIME, 0 /* delay */);
+ }
+ }
+
+ private synchronized void notifyTimedEvent(boolean refreshTime) {
+ // figure out next callback
+ long nowUs;
+ try {
+ nowUs = getCurrentTimeUs(refreshTime, true);
+ } catch (IllegalStateException e) {
+ // assume we paused until new player arrives
+ mRefresh = true;
+ mPausing = true; // this ensures that call succeeds
+ nowUs = getCurrentTimeUs(refreshTime, true);
+ }
+ long nextTimeUs = nowUs;
+
+ if (mSeeking) {
+ // skip timed-event notifications until seek is complete
+ return;
+ }
+
+ if (DEBUG) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("notifyTimedEvent(").append(mLastTimeUs).append(" -> ")
+ .append(nowUs).append(") from {");
+ boolean first = true;
+ for (long time: mTimes) {
+ if (time == NO_TIME) {
+ continue;
+ }
+ if (!first) sb.append(", ");
+ sb.append(time);
+ first = false;
+ }
+ sb.append("}");
+ Log.d(TAG, sb.toString());
+ }
+
+ Vector<MediaTimeProvider.OnMediaTimeListener> activatedListeners =
+ new Vector<MediaTimeProvider.OnMediaTimeListener>();
+ for (int ix = 0; ix < mTimes.length; ix++) {
+ if (mListeners[ix] == null) {
+ break;
+ }
+ if (mTimes[ix] <= NO_TIME) {
+ // ignore, unless we were stopped
+ } else if (mTimes[ix] <= nowUs + MAX_EARLY_CALLBACK_US) {
+ activatedListeners.add(mListeners[ix]);
+ if (DEBUG) Log.d(TAG, "removed");
+ mTimes[ix] = NO_TIME;
+ } else if (nextTimeUs == nowUs || mTimes[ix] < nextTimeUs) {
+ nextTimeUs = mTimes[ix];
+ }
+ }
+
+ if (nextTimeUs > nowUs && !mPaused) {
+ // schedule callback at nextTimeUs
+ if (DEBUG) Log.d(TAG, "scheduling for " + nextTimeUs + " and " + nowUs);
+ mPlayer.notifyAt(nextTimeUs);
+ } else {
+ mEventHandler.removeMessages(NOTIFY);
+ // no more callbacks
+ }
+
+ for (MediaTimeProvider.OnMediaTimeListener listener: activatedListeners) {
+ listener.onTimedEvent(nowUs);
+ }
+ }
+
+ public long getCurrentTimeUs(boolean refreshTime, boolean monotonic)
+ throws IllegalStateException {
+ synchronized (this) {
+ // we always refresh the time when the paused-state changes, because
+ // we expect to have received the pause-change event delayed.
+ if (mPaused && !refreshTime) {
+ return mLastReportedTime;
+ }
+
+ try {
+ mLastTimeUs = mPlayer.getCurrentPosition() * 1000L;
+ mPaused = !mPlayer.isPlaying() || mBuffering;
+ if (DEBUG) Log.v(TAG, (mPaused ? "paused" : "playing") + " at " + mLastTimeUs);
+ } catch (IllegalStateException e) {
+ if (mPausing) {
+ // if we were pausing, get last estimated timestamp
+ mPausing = false;
+ if (!monotonic || mLastReportedTime < mLastTimeUs) {
+ mLastReportedTime = mLastTimeUs;
+ }
+ mPaused = true;
+ if (DEBUG) Log.d(TAG, "illegal state, but pausing: estimating at " + mLastReportedTime);
+ return mLastReportedTime;
+ }
+ // TODO get time when prepared
+ throw e;
+ }
+ if (monotonic && mLastTimeUs < mLastReportedTime) {
+ /* have to adjust time */
+ if (mLastReportedTime - mLastTimeUs > 1000000) {
+ // schedule seeked event if time jumped significantly
+ // TODO: do this properly by introducing an exception
+ mStopped = false;
+ mSeeking = true;
+ scheduleNotification(NOTIFY_SEEK, 0 /* delay */);
+ }
+ } else {
+ mLastReportedTime = mLastTimeUs;
+ }
+
+ return mLastReportedTime;
+ }
+ }
+
+ private class EventHandler extends Handler {
+ public EventHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == NOTIFY) {
+ switch (msg.arg1) {
+ case NOTIFY_TIME:
+ notifyTimedEvent(true /* refreshTime */);
+ break;
+ case NOTIFY_STOP:
+ notifyStop();
+ break;
+ case NOTIFY_SEEK:
+ notifySeek();
+ break;
+ case NOTIFY_TRACK_DATA:
+ notifyTrackData((Pair<SubtitleTrack, byte[]>)msg.obj);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/MediaPlayerBase.java b/media/java/android/media/MediaPlayerBase.java
index 980c70f..0efec901 100644
--- a/media/java/android/media/MediaPlayerBase.java
+++ b/media/java/android/media/MediaPlayerBase.java
@@ -16,49 +16,49 @@
package android.media;
-import android.media.session.PlaybackState;
-import android.os.Handler;
+import android.media.MediaSession2.PlaylistParams;
+
+import java.util.List;
+import java.util.concurrent.Executor;
/**
- * Tentative interface for all media players that want media session.
- * APIs are named to avoid conflicts with MediaPlayer APIs.
- * All calls should be asynchrounous.
- *
+ * Base interfaces for all media players that want media session.
* @hide
*/
-// TODO(wjia) Finalize the list of MediaPlayer2, which MediaPlayerBase's APIs will be come from.
public abstract class MediaPlayerBase {
/**
- * Listens change in {@link PlaybackState}.
+ * Listens change in {@link PlaybackState2}.
*/
public interface PlaybackListener {
/**
- * Called when {@link PlaybackState} for this player is changed.
+ * Called when {@link PlaybackState2} for this player is changed.
*/
- void onPlaybackChanged(PlaybackState state);
+ void onPlaybackChanged(PlaybackState2 state);
}
- // TODO(jaewan): setDataSources()?
- // TODO(jaewan): Add release() or do that in stop()?
-
- // TODO(jaewan): Add set/getSupportedActions().
public abstract void play();
+ public abstract void prepare();
public abstract void pause();
public abstract void stop();
public abstract void skipToPrevious();
public abstract void skipToNext();
+ public abstract void seekTo(long pos);
+ public abstract void fastFoward();
+ public abstract void rewind();
- // Currently PlaybackState's error message is the content title (for testing only)
- // TODO(jaewan): Add metadata support
- public abstract PlaybackState getPlaybackState();
+ public abstract PlaybackState2 getPlaybackState();
+ public abstract AudioAttributes getAudioAttributes();
+
+ public abstract void setPlaylist(List<MediaItem2> item, PlaylistParams param);
+ public abstract void setCurrentPlaylistItem(int index);
/**
* Add a {@link PlaybackListener} to be invoked when the playback state is changed.
*
+ * @param executor the Handler that will receive the listener
* @param listener the listener that will be run
- * @param handler the Handler that will receive the listener
*/
- public abstract void addPlaybackListener(PlaybackListener listener, Handler handler);
+ public abstract void addPlaybackListener(Executor executor, PlaybackListener listener);
/**
* Remove previously added {@link PlaybackListener}.
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 6fdf7a8..7dffd40 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -16,14 +16,14 @@
package android.media;
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.Activity;
+import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
import android.media.MediaPlayerBase.PlaybackListener;
import android.media.session.MediaSession;
import android.media.session.MediaSession.Callback;
@@ -39,8 +39,11 @@
import android.text.TextUtils;
import android.util.ArraySet;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* Allows a media app to expose its transport controls and playback information in a process to
@@ -74,12 +77,6 @@
* @see MediaSessionService2
* @hide
*/
-// TODO(jaewan): Unhide
-// TODO(jaewan): Revisit comments. Currently it's borrowed from the MediaSession.
-// TODO(jaewan): Should we support thread safe? It may cause tricky issue such as b/63797089
-// TODO(jaewan): Should we make APIs for MediaSessionService2 public? It's helpful for
-// developers that doesn't want to override from Browser, but user may not use this
-// correctly.
public class MediaSession2 implements AutoCloseable {
private final MediaSession2Provider mProvider;
@@ -346,7 +343,8 @@
/**
* Called when a controller set rating on the currently playing contents.
*
- * @param
+ * @param controller controller information
+ * @param rating new rating from the controller
*/
public void onSetRating(@NonNull ControllerInfo controller, @NonNull Rating2 rating) { }
@@ -426,7 +424,7 @@
/**
* Override to handle requests to play a specific media item represented by a URI.
*/
- public void prepareFromUri(@NonNull ControllerInfo controller,
+ public void onPrepareFromUri(@NonNull ControllerInfo controller,
@NonNull Uri uri, @Nullable Bundle extras) { }
/**
@@ -461,6 +459,7 @@
final Context mContext;
final MediaPlayerBase mPlayer;
String mId;
+ Executor mCallbackExecutor;
C mCallback;
VolumeProvider mVolumeProvider;
int mRatingType;
@@ -523,7 +522,7 @@
/**
* Set an intent for launching UI for this Session. This can be used as a
* quick link to an ongoing media screen. The intent should be for an
- * activity that may be started using {@link Activity#startActivity(Intent)}.
+ * activity that may be started using {@link Context#startActivity(Intent)}.
*
* @param pi The intent to launch to show UI for this session.
*/
@@ -551,12 +550,21 @@
}
/**
- * Set {@link SessionCallback}.
+ * Set callback for the session.
*
+ * @param executor callback executor
* @param callback session callback.
* @return
*/
- public T setSessionCallback(@Nullable C callback) {
+ public T setSessionCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull C callback) {
+ if (executor == null) {
+ throw new IllegalArgumentException("executor shouldn't be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback shouldn't be null");
+ }
+ mCallbackExecutor = executor;
mCallback = callback;
return (T) this;
}
@@ -568,7 +576,7 @@
* @throws IllegalStateException if the session with the same id is already exists for the
* package.
*/
- public abstract MediaSession2 build() throws IllegalStateException;
+ public abstract MediaSession2 build();
}
/**
@@ -586,12 +594,12 @@
}
@Override
- public MediaSession2 build() throws IllegalStateException {
+ public MediaSession2 build() {
if (mCallback == null) {
mCallback = new SessionCallback();
}
- return new MediaSession2(mContext, mPlayer, mId, mCallback,
- mVolumeProvider, mRatingType, mSessionActivity);
+ return new MediaSession2(mContext, mPlayer, mId, mVolumeProvider, mRatingType,
+ mSessionActivity, mCallbackExecutor, mCallback);
}
}
@@ -611,7 +619,7 @@
IMediaSession2Callback callback) {
mProvider = ApiLoader.getProvider(context)
.createMediaSession2ControllerInfoProvider(
- this, context, uid, pid, packageName, callback);
+ context, this, uid, pid, packageName, callback);
}
/**
@@ -639,11 +647,7 @@
return mProvider.isTrusted_impl();
}
- /**
- * @hide
- * @return
- */
- // TODO(jaewan): SystemApi
+ @SystemApi
public ControllerInfoProvider getProvider() {
return mProvider;
}
@@ -842,28 +846,79 @@
* Parameter for the playlist.
*/
// TODO(jaewan): add fromBundle()/toBundle()
- public class PlaylistParam {
+ public static class PlaylistParams {
+ /**
+ * @hide
+ */
+ @IntDef({REPEAT_MODE_NONE, REPEAT_MODE_ONE, REPEAT_MODE_ALL,
+ REPEAT_MODE_GROUP})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RepeatMode {}
+
+ /**
+ * Playback will be stopped at the end of the playing media list.
+ */
+ public static final int REPEAT_MODE_NONE = 0;
+
+ /**
+ * Playback of the current playing media item will be repeated.
+ */
+ public static final int REPEAT_MODE_ONE = 1;
+
+ /**
+ * Playing media list will be repeated.
+ */
+ public static final int REPEAT_MODE_ALL = 2;
+
+ /**
+ * Playback of the playing media group will be repeated.
+ * A group is a logical block of media items which is specified in the section 5.7 of the
+ * Bluetooth AVRCP 1.6.
+ */
+ public static final int REPEAT_MODE_GROUP = 3;
+
+ /**
+ * @hide
+ */
+ @IntDef({SHUFFLE_MODE_NONE, SHUFFLE_MODE_ALL, SHUFFLE_MODE_GROUP})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShuffleMode {}
+
+ /**
+ * Media list will be played in order.
+ */
+ public static final int SHUFFLE_MODE_NONE = 0;
+
+ /**
+ * Media list will be played in shuffled order.
+ */
+ public static final int SHUFFLE_MODE_ALL = 1;
+
+ /**
+ * Media group will be played in shuffled order.
+ * A group is a logical block of media items which is specified in the section 5.7 of the
+ * Bluetooth AVRCP 1.6.
+ */
+ public static final int SHUFFLE_MODE_GROUP = 2;
+
+ private @RepeatMode int mRepeatMode;
+ private @ShuffleMode int mShuffleMode;
+
private MediaMetadata2 mPlaylistMetadata;
- // It's mixture of shuffle mode and repeat mode. Needs to be separated for avrcp 1.6 support
- // PlaybackState#REPEAT_MODE_ALL
- // PlaybackState#REPEAT_MODE_GROUP <- for avrcp 1.6
- // PlaybackState#REPEAT_MODE_INVALID
- // PlaybackState#REPEAT_MODE_NONE
- // PlaybackState#REPEAT_MODE_ONE
- // PlaybackState#SHUFFLE_MODE_ALL
- // PlaybackState#SHUFFLE_MODE_GROUP <- for avrcp 1.6
- // PlaybackState#SHUFFLE_MODE_INVALID
- // PlaybackState#SHUFFLE_MODE_NONE
- private int mLoopingMode;
-
- public PlaylistParam(int loopingMode, @Nullable MediaMetadata2 playlistMetadata) {
- mLoopingMode = loopingMode;
+ public PlaylistParams(@RepeatMode int repeatMode, @ShuffleMode int shuffleMode,
+ @Nullable MediaMetadata2 playlistMetadata) {
+ mRepeatMode = repeatMode;
+ mShuffleMode = shuffleMode;
mPlaylistMetadata = playlistMetadata;
}
- public int getLoopingMode() {
- return mLoopingMode;
+ public @RepeatMode int getRepeatMode() {
+ return mRepeatMode;
+ }
+
+ public @ShuffleMode int getShuffleMode() {
+ return mShuffleMode;
}
public MediaMetadata2 getPlaylistMetadata() {
@@ -885,26 +940,25 @@
* framework had to add heuristics to figure out if an app is
* @hide
*/
- MediaSession2(Context context, MediaPlayerBase player, String id,
- SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
- PendingIntent sessionActivity) {
+
+ MediaSession2(Context context, MediaPlayerBase player, String id, VolumeProvider volumeProvider,
+ int ratingType, PendingIntent sessionActivity, Executor callbackExecutor,
+ SessionCallback callback) {
super();
- mProvider = createProvider(context, player, id, callback,
- volumeProvider, ratingType, sessionActivity);
+ mProvider = createProvider(context, player, id, volumeProvider, ratingType, sessionActivity,
+ callbackExecutor, callback
+ );
}
MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
- SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
- PendingIntent sessionActivity) {
+ VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+ Executor callbackExecutor, SessionCallback callback) {
return ApiLoader.getProvider(context)
- .createMediaSession2(this, context, player, id, callback,
- volumeProvider, ratingType, sessionActivity);
+ .createMediaSession2(context, this, player, id, volumeProvider, ratingType,
+ sessionActivity, callbackExecutor, callback);
}
- /**
- * @hide
- */
- // TODO(jaewan): SystemApi
+ @SystemApi
public MediaSession2Provider getProvider() {
return mProvider;
}
@@ -934,10 +988,8 @@
* @param volumeProvider a volume provider
* @see #setPlayer(MediaPlayerBase)
* @see Builder#setVolumeProvider(VolumeProvider)
- * @throws IllegalArgumentException if a parameter is {@code null}.
*/
- public void setPlayer(@NonNull MediaPlayerBase player, @NonNull VolumeProvider volumeProvider)
- throws IllegalArgumentException {
+ public void setPlayer(@NonNull MediaPlayerBase player, @NonNull VolumeProvider volumeProvider) {
mProvider.setPlayer_impl(player, volumeProvider);
}
@@ -954,10 +1006,10 @@
}
/**
- * Returns the {@link SessionToken} for creating {@link MediaController2}.
+ * Returns the {@link SessionToken2} for creating {@link MediaController2}.
*/
public @NonNull
- SessionToken getToken() {
+ SessionToken2 getToken() {
return mProvider.getToken_impl();
}
@@ -1153,7 +1205,7 @@
// To match with KEYCODE_MEDIA_SKIP_BACKWARD
}
- public void setPlaylist(@NonNull List<MediaItem2> playlist, @NonNull PlaylistParam param) {
+ public void setPlaylist(@NonNull List<MediaItem2> playlist, @NonNull PlaylistParams param) {
mProvider.setPlaylist_impl(playlist, param);
}
}
diff --git a/media/java/android/media/MediaSessionService2.java b/media/java/android/media/MediaSessionService2.java
index 1a12d68..6b2de06 100644
--- a/media/java/android/media/MediaSessionService2.java
+++ b/media/java/android/media/MediaSessionService2.java
@@ -23,7 +23,6 @@
import android.app.Service;
import android.content.Intent;
import android.media.MediaSession2.ControllerInfo;
-import android.media.session.PlaybackState;
import android.media.update.ApiLoader;
import android.media.update.MediaSessionService2Provider;
import android.os.IBinder;
@@ -89,7 +88,7 @@
* rejected, the controller will unbind. If it's accepted, the controller will be available to use
* and keep binding.
* <p>
- * When playback is started for this session service, {@link #onUpdateNotification(PlaybackState)}
+ * When playback is started for this session service, {@link #onUpdateNotification(PlaybackState2)}
* is called and service would become a foreground service. It's needed to keep playback after the
* controller is destroyed. The session service becomes background service when the playback is
* stopped.
@@ -99,20 +98,8 @@
* Any app can bind to the session service with controller, but the controller can be used only if
* the session service accepted the connection request through
* {@link MediaSession2.SessionCallback#onConnect(ControllerInfo)}.
- *
* @hide
*/
-// TODO(jaewan): Unhide
-// TODO(jaewan): Can we clean up sessions in onDestroy() automatically instead?
-// What about currently running SessionCallback when the onDestroy() is called?
-// TODO(jaewan): Protect this with system|privilleged permission - Q.
-// TODO(jaewan): Add permission check for the service to know incoming connection request.
-// Follow-up questions: What about asking a XML for list of white/black packages for
-// allowing enumeration?
-// We can read the information even when the service is started,
-// so SessionManager.getXXXXService() can only return apps
-// TODO(jaewan): Will be the black/white listing persistent?
-// In other words, can we cache the rejection?
public abstract class MediaSessionService2 extends Service {
private final MediaSessionService2Provider mProvider;
@@ -179,7 +166,7 @@
* @return a {@link MediaNotification}. If it's {@code null}, notification wouldn't be shown.
*/
// TODO(jaewan): Also add metadata
- public MediaNotification onUpdateNotification(PlaybackState state) {
+ public MediaNotification onUpdateNotification(PlaybackState2 state) {
return mProvider.onUpdateNotification_impl(state);
}
@@ -213,7 +200,7 @@
}
/**
- * Returned by {@link #onUpdateNotification(PlaybackState)} for making session service
+ * Returned by {@link #onUpdateNotification(PlaybackState2)} for making session service
* foreground service to keep playback running in the background. It's highly recommended to
* show media style notification here.
*/
diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java
index 3740aea..04f211d 100644
--- a/media/java/android/media/PlaybackState2.java
+++ b/media/java/android/media/PlaybackState2.java
@@ -17,6 +17,7 @@
package android.media;
import android.annotation.IntDef;
+import android.os.Bundle;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -27,11 +28,11 @@
* the current playback position and extra.
* @hide
*/
-// TODO(jaewan): Move to updatable
-// TODO(jaewan): Add fromBundle() toBundle()
public final class PlaybackState2 {
private static final String TAG = "PlaybackState2";
+ private static final String KEY_STATE = "android.media.playbackstate2.state";
+
// TODO(jaewan): Replace states from MediaPlayer2
/**
* @hide
@@ -97,7 +98,7 @@
private final long mUpdateTime;
private final long mActiveItemId;
- private PlaybackState2(int state, long position, long updateTime, float speed,
+ public PlaybackState2(int state, long position, long updateTime, float speed,
long bufferedPosition, long activeItemId, CharSequence error) {
mState = state;
mPosition = position;
@@ -129,14 +130,8 @@
* <li> {@link PlaybackState2#STATE_STOPPED}</li>
* <li> {@link PlaybackState2#STATE_PLAYING}</li>
* <li> {@link PlaybackState2#STATE_PAUSED}</li>
- * <li> {@link PlaybackState2#STATE_FAST_FORWARDING}</li>
- * <li> {@link PlaybackState2#STATE_REWINDING}</li>
* <li> {@link PlaybackState2#STATE_BUFFERING}</li>
* <li> {@link PlaybackState2#STATE_ERROR}</li>
- * <li> {@link PlaybackState2#STATE_CONNECTING}</li>
- * <li> {@link PlaybackState2#STATE_SKIPPING_TO_PREVIOUS}</li>
- * <li> {@link PlaybackState2#STATE_SKIPPING_TO_NEXT}</li>
- * <li> {@link PlaybackState2#STATE_SKIPPING_TO_QUEUE_ITEM}</li>
* </ul>
*/
@State
@@ -197,4 +192,24 @@
public long getCurrentPlaylistItemIndex() {
return mActiveItemId;
}
+
+ /**
+ * @return Bundle object for this to share between processes.
+ */
+ public Bundle toBundle() {
+ // TODO(jaewan): Include other variables.
+ Bundle bundle = new Bundle();
+ bundle.putInt(KEY_STATE, mState);
+ return bundle;
+ }
+
+ /**
+ * @param bundle input
+ * @return
+ */
+ public static PlaybackState2 fromBundle(Bundle bundle) {
+ // TODO(jaewan): Include other variables.
+ final int state = bundle.getInt(KEY_STATE);
+ return new PlaybackState2(state, 0, 0, 0, 0, 0, null);
+ }
}
\ No newline at end of file
diff --git a/media/java/android/media/Rating2.java b/media/java/android/media/Rating2.java
index 67e5e72..93aea6f 100644
--- a/media/java/android/media/Rating2.java
+++ b/media/java/android/media/Rating2.java
@@ -32,7 +32,6 @@
* through one of the factory methods.
* @hide
*/
-// TODO(jaewan): Move this to updatable
public final class Rating2 {
private static final String TAG = "Rating2";
diff --git a/media/java/android/media/SessionToken.java b/media/java/android/media/SessionToken2.java
similarity index 94%
rename from media/java/android/media/SessionToken.java
rename to media/java/android/media/SessionToken2.java
index 53fc24a..0abb852 100644
--- a/media/java/android/media/SessionToken.java
+++ b/media/java/android/media/SessionToken2.java
@@ -37,10 +37,8 @@
* It can be also obtained by {@link MediaSessionManager}.
* @hide
*/
-// TODO(jaewan): Unhide. SessionToken2?
// TODO(jaewan): Move Token to updatable!
-// TODO(jaewan): Find better name for this (SessionToken or Session2Token)
-public final class SessionToken {
+public final class SessionToken2 {
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE})
public @interface TokenType {
@@ -75,7 +73,7 @@
*/
// TODO(jaewan): UID is also needed.
// TODO(jaewan): Unhide
- public SessionToken(@TokenType int type, @NonNull String packageName, @NonNull String id,
+ public SessionToken2(@TokenType int type, @NonNull String packageName, @NonNull String id,
@Nullable String serviceName, @Nullable IMediaSession2 sessionBinder) {
// TODO(jaewan): Add sanity check.
mType = type;
@@ -102,7 +100,7 @@
return false;
if (getClass() != obj.getClass())
return false;
- SessionToken other = (SessionToken) obj;
+ SessionToken2 other = (SessionToken2) obj;
if (!mPackageName.equals(other.getPackageName())
|| !mServiceName.equals(other.getServiceName())
|| !mId.equals(other.getId())
@@ -168,7 +166,7 @@
* @param bundle
* @return
*/
- public static SessionToken fromBundle(@NonNull Bundle bundle) {
+ public static SessionToken2 fromBundle(@NonNull Bundle bundle) {
if (bundle == null) {
return null;
}
@@ -202,7 +200,7 @@
// TODO(jaewan): Revisit here when we add connection callback to the session for individual
// controller's permission check. With it, sessionBinder should be available
// if and only if for session, not session service.
- return new SessionToken(type, packageName, id, serviceName,
+ return new SessionToken2(type, packageName, id, serviceName,
sessionBinder != null ? IMediaSession2.Stub.asInterface(sessionBinder) : null);
}
@@ -210,7 +208,6 @@
* Create a {@link Bundle} from this token to share it across processes.
*
* @return Bundle
- * @hide
*/
public Bundle toBundle() {
Bundle bundle = new Bundle();
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index 6a9f04a..81b4603 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -28,8 +28,7 @@
import android.media.IRemoteVolumeController;
import android.media.MediaSession2;
import android.media.MediaSessionService2;
-import android.media.SessionToken;
-import android.media.session.ISessionManager;
+import android.media.SessionToken2;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -342,11 +341,11 @@
* @hide
*/
// TODO(jaewan): System API
- public SessionToken createSessionToken(@NonNull String callingPackage, @NonNull String id,
+ public SessionToken2 createSessionToken(@NonNull String callingPackage, @NonNull String id,
@NonNull IMediaSession2 binder) {
try {
Bundle bundle = mService.createSessionToken(callingPackage, id, binder);
- return SessionToken.fromBundle(bundle);
+ return SessionToken2.fromBundle(bundle);
} catch (RemoteException e) {
Log.wtf(TAG, "Cannot communicate with the service.", e);
}
@@ -354,7 +353,7 @@
}
/**
- * Get {@link List} of {@link SessionToken} whose sessions are active now. This list represents
+ * Get {@link List} of {@link SessionToken2} whose sessions are active now. This list represents
* active sessions regardless of whether they're {@link MediaSession2} or
* {@link MediaSessionService2}.
*
@@ -364,7 +363,7 @@
// TODO(jaewan): Unhide
// TODO(jaewan): Protect this with permission.
// TODO(jaewna): Add listener for change in lists.
- public List<SessionToken> getActiveSessionTokens() {
+ public List<SessionToken2> getActiveSessionTokens() {
try {
List<Bundle> bundles = mService.getSessionTokens(
/* activeSessionOnly */ true, /* sessionServiceOnly */ false);
@@ -376,7 +375,7 @@
}
/**
- * Get {@link List} of {@link SessionToken} for {@link MediaSessionService2} regardless of their
+ * Get {@link List} of {@link SessionToken2} for {@link MediaSessionService2} regardless of their
* activeness. This list represents media apps that support background playback.
*
* @return list of Tokens
@@ -384,7 +383,7 @@
*/
// TODO(jaewan): Unhide
// TODO(jaewna): Add listener for change in lists.
- public List<SessionToken> getSessionServiceTokens() {
+ public List<SessionToken2> getSessionServiceTokens() {
try {
List<Bundle> bundles = mService.getSessionTokens(
/* activeSessionOnly */ false, /* sessionServiceOnly */ true);
@@ -396,7 +395,7 @@
}
/**
- * Get all {@link SessionToken}s. This is the combined list of {@link #getActiveSessionTokens()}
+ * Get all {@link SessionToken2}s. This is the combined list of {@link #getActiveSessionTokens()}
* and {@link #getSessionServiceTokens}.
*
* @return list of Tokens
@@ -407,7 +406,7 @@
// TODO(jaewan): Unhide
// TODO(jaewan): Protect this with permission.
// TODO(jaewna): Add listener for change in lists.
- public List<SessionToken> getAllSessionTokens() {
+ public List<SessionToken2> getAllSessionTokens() {
try {
List<Bundle> bundles = mService.getSessionTokens(
/* activeSessionOnly */ false, /* sessionServiceOnly */ false);
@@ -418,11 +417,11 @@
}
}
- private static List<SessionToken> toTokenList(List<Bundle> bundles) {
- List<SessionToken> tokens = new ArrayList<>();
+ private static List<SessionToken2> toTokenList(List<Bundle> bundles) {
+ List<SessionToken2> tokens = new ArrayList<>();
if (bundles != null) {
for (int i = 0; i < bundles.size(); i++) {
- SessionToken token = SessionToken.fromBundle(bundles.get(i));
+ SessionToken2 token = SessionToken2.fromBundle(bundles.get(i));
if (token != null) {
tokens.add(token);
}
diff --git a/media/java/android/media/update/MediaBrowser2Provider.java b/media/java/android/media/update/MediaBrowser2Provider.java
index e48711d..67680c7 100644
--- a/media/java/android/media/update/MediaBrowser2Provider.java
+++ b/media/java/android/media/update/MediaBrowser2Provider.java
@@ -16,6 +16,7 @@
package android.media.update;
+import android.annotation.SystemApi;
import android.os.Bundle;
/**
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
index 8f5a643..8dfb892 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -16,14 +16,15 @@
package android.media.update;
+import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.media.MediaController2.PlaybackInfo;
import android.media.MediaItem2;
import android.media.MediaSession2.Command;
-import android.media.MediaSession2.PlaylistParam;
+import android.media.MediaSession2.PlaylistParams;
import android.media.PlaybackState2;
import android.media.Rating2;
-import android.media.SessionToken;
+import android.media.SessionToken2;
import android.net.Uri;
import android.os.Bundle;
import android.os.ResultReceiver;
@@ -35,7 +36,7 @@
*/
public interface MediaController2Provider extends TransportControlProvider {
void close_impl();
- SessionToken getSessionToken_impl();
+ SessionToken2 getSessionToken_impl();
boolean isConnected_impl();
PendingIntent getSessionActivity_impl();
@@ -59,6 +60,6 @@
void removePlaylistItem_impl(MediaItem2 index);
void addPlaylistItem_impl(int index, MediaItem2 item);
- PlaylistParam getPlaylistParam_impl();
+ PlaylistParams getPlaylistParam_impl();
PlaybackState2 getPlaybackState_impl();
}
diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java
index dac5784..a568839 100644
--- a/media/java/android/media/update/MediaLibraryService2Provider.java
+++ b/media/java/android/media/update/MediaLibraryService2Provider.java
@@ -16,8 +16,11 @@
package android.media.update;
+import android.annotation.SystemApi;
import android.media.MediaSession2.ControllerInfo;
-import android.os.Bundle; /**
+import android.os.Bundle;
+
+/**
* @hide
*/
public interface MediaLibraryService2Provider extends MediaSessionService2Provider {
diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java
index 511686d..d32b741 100644
--- a/media/java/android/media/update/MediaSession2Provider.java
+++ b/media/java/android/media/update/MediaSession2Provider.java
@@ -16,20 +16,18 @@
package android.media.update;
+import android.annotation.SystemApi;
import android.media.AudioAttributes;
import android.media.MediaItem2;
import android.media.MediaPlayerBase;
-import android.media.MediaSession2;
import android.media.MediaSession2.Command;
import android.media.MediaSession2.CommandButton;
import android.media.MediaSession2.CommandGroup;
import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.PlaylistParam;
-import android.media.SessionToken;
+import android.media.MediaSession2.PlaylistParams;
+import android.media.SessionToken2;
import android.media.VolumeProvider;
-import android.media.session.PlaybackState;
import android.os.Bundle;
-import android.os.Handler;
import android.os.ResultReceiver;
import java.util.List;
@@ -42,7 +40,7 @@
void setPlayer_impl(MediaPlayerBase player);
void setPlayer_impl(MediaPlayerBase player, VolumeProvider volumeProvider);
MediaPlayerBase getPlayer_impl();
- SessionToken getToken_impl();
+ SessionToken2 getToken_impl();
List<ControllerInfo> getConnectedControllers_impl();
void setCustomLayout_impl(ControllerInfo controller, List<CommandButton> layout);
void setAudioAttributes_impl(AudioAttributes attributes);
@@ -53,11 +51,8 @@
void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
ResultReceiver receiver);
void sendCustomCommand_impl(Command command, Bundle args);
- void setPlaylist_impl(List<MediaItem2> playlist, MediaSession2.PlaylistParam param);
+ void setPlaylist_impl(List<MediaItem2> playlist, PlaylistParams param);
- /**
- * @hide
- */
interface ControllerInfoProvider {
String getPackageName_impl();
int getUid_impl();
diff --git a/media/java/android/media/update/MediaSessionService2Provider.java b/media/java/android/media/update/MediaSessionService2Provider.java
index 1174915..9455da7 100644
--- a/media/java/android/media/update/MediaSessionService2Provider.java
+++ b/media/java/android/media/update/MediaSessionService2Provider.java
@@ -16,11 +16,11 @@
package android.media.update;
+import android.annotation.SystemApi;
import android.content.Intent;
import android.media.MediaSession2;
import android.media.MediaSessionService2.MediaNotification;
-import android.media.session.PlaybackState;
-import android.os.Handler;
+import android.media.PlaybackState2;
import android.os.IBinder;
/**
@@ -28,7 +28,7 @@
*/
public interface MediaSessionService2Provider {
MediaSession2 getSession_impl();
- MediaNotification onUpdateNotification_impl(PlaybackState state);
+ MediaNotification onUpdateNotification_impl(PlaybackState2 state);
// Service
void onCreate_impl();
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 64968d6..3cd1a99 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -17,9 +17,9 @@
package android.media.update;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.content.Context;
-import android.media.IMediaSession2Callback;
import android.media.MediaBrowser2;
import android.media.MediaBrowser2.BrowserCallback;
import android.media.MediaController2;
@@ -31,10 +31,11 @@
import android.media.MediaSession2;
import android.media.MediaSession2.SessionCallback;
import android.media.MediaSessionService2;
-import android.media.SessionToken;
+import android.media.SessionToken2;
import android.media.VolumeProvider;
import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
import android.media.update.MediaSession2Provider.ControllerInfoProvider;
+import android.os.IInterface;
import android.util.AttributeSet;
import android.widget.MediaControlView2;
import android.widget.VideoView2;
@@ -46,10 +47,8 @@
*
* This interface provides access to constructors and static methods that are otherwise not directly
* accessible via an implementation object.
- *
* @hide
*/
-// TODO @SystemApi
public interface StaticProvider {
MediaControlView2Provider createMediaControlView2(
MediaControlView2 instance, ViewProvider superProvider);
@@ -57,25 +56,20 @@
VideoView2 instance, ViewProvider superProvider,
@Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);
- MediaSession2Provider createMediaSession2(MediaSession2 mediaSession2, Context context,
- MediaPlayerBase player, String id, SessionCallback callback,
- VolumeProvider volumeProvider, int ratingType,
- PendingIntent sessionActivity);
- ControllerInfoProvider createMediaSession2ControllerInfoProvider(
- MediaSession2.ControllerInfo instance, Context context, int uid, int pid,
- String packageName, IMediaSession2Callback callback);
- MediaController2Provider createMediaController2(
- MediaController2 instance, Context context, SessionToken token,
- ControllerCallback callback, Executor executor);
- MediaBrowser2Provider createMediaBrowser2(
- MediaBrowser2 instance, Context context, SessionToken token,
- BrowserCallback callback, Executor executor);
- MediaSessionService2Provider createMediaSessionService2(
- MediaSessionService2 instance);
- MediaSessionService2Provider createMediaLibraryService2(
- MediaLibraryService2 instance);
- MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(
- MediaLibrarySession instance, Context context, MediaPlayerBase player, String id,
- MediaLibrarySessionCallback callback, VolumeProvider volumeProvider, int ratingType,
- PendingIntent sessionActivity);
+ MediaSession2Provider createMediaSession2(Context context, MediaSession2 instance,
+ MediaPlayerBase player, String id, VolumeProvider volumeProvider, int ratingType,
+ PendingIntent sessionActivity, Executor executor, SessionCallback callback);
+ ControllerInfoProvider createMediaSession2ControllerInfoProvider(Context context,
+ MediaSession2.ControllerInfo instance, int uid, int pid,
+ String packageName, IInterface callback);
+ MediaController2Provider createMediaController2(Context context, MediaController2 instance,
+ SessionToken2 token, Executor executor, ControllerCallback callback);
+ MediaBrowser2Provider createMediaBrowser2(Context context, MediaBrowser2 instance,
+ SessionToken2 token, Executor executor, BrowserCallback callback);
+ MediaSessionService2Provider createMediaSessionService2(MediaSessionService2 instance);
+ MediaSessionService2Provider createMediaLibraryService2(MediaLibraryService2 instance);
+ MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(Context context,
+ MediaLibrarySession instance, MediaPlayerBase player, String id,
+ VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+ Executor executor, MediaLibrarySessionCallback callback);
}
diff --git a/media/java/android/media/update/TransportControlProvider.java b/media/java/android/media/update/TransportControlProvider.java
index 5217a9d..0c87063e 100644
--- a/media/java/android/media/update/TransportControlProvider.java
+++ b/media/java/android/media/update/TransportControlProvider.java
@@ -16,14 +16,9 @@
package android.media.update;
-import android.media.MediaPlayerBase;
-import android.media.session.PlaybackState;
-import android.os.Handler;
-
/**
* @hide
*/
-// TODO(jaewan): SystemApi
public interface TransportControlProvider {
void play_impl();
void pause_impl();
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 597336b..74f3aca 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -12,7 +12,6 @@
"android_media_MediaDrm.cpp",
"android_media_MediaExtractor.cpp",
"android_media_MediaHTTPConnection.cpp",
- "android_media_MediaMetricsJNI.cpp",
"android_media_MediaMetadataRetriever.cpp",
"android_media_MediaMuxer.cpp",
"android_media_MediaPlayer.cpp",
@@ -84,6 +83,92 @@
],
}
+cc_library_shared {
+ name: "libmedia2_jni",
+
+ srcs: [
+ "android_media_Media2HTTPConnection.cpp",
+ "android_media_Media2HTTPService.cpp",
+ "android_media_MediaCrypto.cpp",
+ "android_media_Media2DataSource.cpp",
+ "android_media_MediaDrm.cpp",
+ "android_media_MediaPlayer2.cpp",
+ "android_media_SyncParams.cpp",
+ ],
+
+ shared_libs: [
+ "android.hardware.cas@1.0", // for CasManager. VNDK???
+ "android.hardware.cas.native@1.0", // CasManager. VNDK???
+ "libandroid", // NDK
+ "libandroid_runtime", // ???
+ "libaudioclient", // for use of AudioTrack, AudioSystem. to be removed
+ "liblog", // NDK
+ "libdrmframework", // for FileSource, MediaHTTP
+ "libgui", // for VideoFrameScheduler
+ "libhidlbase", // VNDK???
+ "libmediandk", // NDK
+ "libpowermanager", // for JWakeLock. to be removed
+ ],
+
+ header_libs: ["libhardware_headers"],
+
+ static_libs: [
+ "libbacktrace",
+ "libbase",
+ "libbinder",
+ "libc_malloc_debug_backtrace",
+ "libcrypto",
+ "libcutils",
+ "libdexfile",
+ "liblzma",
+ "libmedia",
+ "libmedia_helper",
+ "libmedia_player2",
+ "libmedia_player2_util",
+ "libmediadrm",
+ "libmediaextractor",
+ "libmediametrics",
+ "libmediautils",
+ "libnativehelper",
+ "libnetd_client",
+ "libstagefright_esds",
+ "libstagefright_foundation",
+ "libstagefright_httplive",
+ "libstagefright_id3",
+ "libstagefright_mpeg2support",
+ "libstagefright_nuplayer2",
+ "libstagefright_player2",
+ "libstagefright_rtsp",
+ "libstagefright_timedtext",
+ "libunwindstack",
+ "libutils",
+ "libutilscallstack",
+ "libvndksupport",
+ "libz",
+ "libziparchive",
+ ],
+
+ group_static_libs: true,
+
+ include_dirs: [
+ "frameworks/base/core/jni",
+ "frameworks/native/include/media/openmax",
+ "system/media/camera/include",
+ ],
+
+ export_include_dirs: ["."],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wno-error=deprecated-declarations",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+
+ ldflags: ["-Wl,--exclude-libs=ALL"],
+}
+
subdirs = [
"audioeffect",
"soundpool",
diff --git a/media/jni/android_media_Media2DataSource.cpp b/media/jni/android_media_Media2DataSource.cpp
new file mode 100644
index 0000000..bc3f6bd
--- /dev/null
+++ b/media/jni/android_media_Media2DataSource.cpp
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "JMedia2DataSource-JNI"
+#include <utils/Log.h>
+
+#include "android_media_Media2DataSource.h"
+
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/Log.h"
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+
+#include <drm/drm_framework_common.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+namespace android {
+
+static const size_t kBufferSize = 64 * 1024;
+
+JMedia2DataSource::JMedia2DataSource(JNIEnv* env, jobject source)
+ : mJavaObjStatus(OK),
+ mSizeIsCached(false),
+ mCachedSize(0) {
+ mMedia2DataSourceObj = env->NewGlobalRef(source);
+ CHECK(mMedia2DataSourceObj != NULL);
+
+ ScopedLocalRef<jclass> media2DataSourceClass(env, env->GetObjectClass(mMedia2DataSourceObj));
+ CHECK(media2DataSourceClass.get() != NULL);
+
+ mReadAtMethod = env->GetMethodID(media2DataSourceClass.get(), "readAt", "(J[BII)I");
+ CHECK(mReadAtMethod != NULL);
+ mGetSizeMethod = env->GetMethodID(media2DataSourceClass.get(), "getSize", "()J");
+ CHECK(mGetSizeMethod != NULL);
+ mCloseMethod = env->GetMethodID(media2DataSourceClass.get(), "close", "()V");
+ CHECK(mCloseMethod != NULL);
+
+ ScopedLocalRef<jbyteArray> tmp(env, env->NewByteArray(kBufferSize));
+ mByteArrayObj = (jbyteArray)env->NewGlobalRef(tmp.get());
+ CHECK(mByteArrayObj != NULL);
+}
+
+JMedia2DataSource::~JMedia2DataSource() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(mMedia2DataSourceObj);
+ env->DeleteGlobalRef(mByteArrayObj);
+}
+
+status_t JMedia2DataSource::initCheck() const {
+ return OK;
+}
+
+ssize_t JMedia2DataSource::readAt(off64_t offset, void *data, size_t size) {
+ Mutex::Autolock lock(mLock);
+
+ if (mJavaObjStatus != OK) {
+ return -1;
+ }
+ if (size > kBufferSize) {
+ size = kBufferSize;
+ }
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jint numread = env->CallIntMethod(mMedia2DataSourceObj, mReadAtMethod,
+ (jlong)offset, mByteArrayObj, (jint)0, (jint)size);
+ if (env->ExceptionCheck()) {
+ ALOGW("An exception occurred in readAt()");
+ LOGW_EX(env);
+ env->ExceptionClear();
+ mJavaObjStatus = UNKNOWN_ERROR;
+ return -1;
+ }
+ if (numread < 0) {
+ if (numread != -1) {
+ ALOGW("An error occurred in readAt()");
+ mJavaObjStatus = UNKNOWN_ERROR;
+ return -1;
+ } else {
+ // numread == -1 indicates EOF
+ return 0;
+ }
+ }
+ if ((size_t)numread > size) {
+ ALOGE("readAt read too many bytes.");
+ mJavaObjStatus = UNKNOWN_ERROR;
+ return -1;
+ }
+
+ ALOGV("readAt %lld / %zu => %d.", (long long)offset, size, numread);
+ env->GetByteArrayRegion(mByteArrayObj, 0, numread, (jbyte*)data);
+ return numread;
+}
+
+status_t JMedia2DataSource::getSize(off64_t* size) {
+ Mutex::Autolock lock(mLock);
+
+ if (mJavaObjStatus != OK) {
+ return UNKNOWN_ERROR;
+ }
+ if (mSizeIsCached) {
+ *size = mCachedSize;
+ return OK;
+ }
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ *size = env->CallLongMethod(mMedia2DataSourceObj, mGetSizeMethod);
+ if (env->ExceptionCheck()) {
+ ALOGW("An exception occurred in getSize()");
+ LOGW_EX(env);
+ env->ExceptionClear();
+ // After returning an error, size shouldn't be used by callers.
+ *size = UNKNOWN_ERROR;
+ mJavaObjStatus = UNKNOWN_ERROR;
+ return UNKNOWN_ERROR;
+ }
+
+ // The minimum size should be -1, which indicates unknown size.
+ if (*size < 0) {
+ *size = -1;
+ }
+
+ mCachedSize = *size;
+ mSizeIsCached = true;
+ return OK;
+}
+
+void JMedia2DataSource::close() {
+ Mutex::Autolock lock(mLock);
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mMedia2DataSourceObj, mCloseMethod);
+ // The closed state is effectively the same as an error state.
+ mJavaObjStatus = UNKNOWN_ERROR;
+}
+
+String8 JMedia2DataSource::toString() {
+ return String8::format("JMedia2DataSource(pid %d, uid %d)", getpid(), getuid());
+}
+
+String8 JMedia2DataSource::getMIMEType() const {
+ return String8("application/octet-stream");
+}
+
+} // namespace android
diff --git a/media/jni/android_media_Media2DataSource.h b/media/jni/android_media_Media2DataSource.h
new file mode 100644
index 0000000..dc085f3
--- /dev/null
+++ b/media/jni/android_media_Media2DataSource.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2017, 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.
+ */
+
+#ifndef _ANDROID_MEDIA_MEDIA2DATASOURCE_H_
+#define _ANDROID_MEDIA_MEDIA2DATASOURCE_H_
+
+#include "jni.h"
+
+#include <media/DataSource.h>
+#include <media/stagefright/foundation/ABase.h>
+#include <utils/Errors.h>
+#include <utils/Mutex.h>
+
+namespace android {
+
+// The native counterpart to a Java android.media.Media2DataSource. It inherits from
+// DataSource.
+//
+// If the java DataSource returns an error or throws an exception it
+// will be considered to be in a broken state, and the only further call this
+// will make is to close().
+class JMedia2DataSource : public DataSource {
+public:
+ JMedia2DataSource(JNIEnv *env, jobject source);
+ virtual ~JMedia2DataSource();
+
+ virtual status_t initCheck() const override;
+ virtual ssize_t readAt(off64_t offset, void *data, size_t size) override;
+ virtual status_t getSize(off64_t *size) override;
+
+ virtual String8 toString() override;
+ virtual String8 getMIMEType() const override;
+ virtual void close() override;
+private:
+ // Protect all member variables with mLock because this object will be
+ // accessed on different threads.
+ Mutex mLock;
+
+ // The status of the java DataSource. Set to OK unless an error occurred or
+ // close() was called.
+ status_t mJavaObjStatus;
+ // Only call the java getSize() once so the app can't change the size on us.
+ bool mSizeIsCached;
+ off64_t mCachedSize;
+
+ jobject mMedia2DataSourceObj;
+ jmethodID mReadAtMethod;
+ jmethodID mGetSizeMethod;
+ jmethodID mCloseMethod;
+ jbyteArray mByteArrayObj;
+
+ DISALLOW_EVIL_CONSTRUCTORS(JMedia2DataSource);
+};
+
+} // namespace android
+
+#endif // _ANDROID_MEDIA_MEDIA2DATASOURCE_H_
diff --git a/media/jni/android_media_Media2HTTPConnection.cpp b/media/jni/android_media_Media2HTTPConnection.cpp
new file mode 100644
index 0000000..60176e3
--- /dev/null
+++ b/media/jni/android_media_Media2HTTPConnection.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Media2HTTPConnection-JNI"
+#include <utils/Log.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include "android_media_Media2HTTPConnection.h"
+#include "android_util_Binder.h"
+
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/Log.h"
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+
+namespace android {
+
+static const size_t kBufferSize = 32768;
+
+JMedia2HTTPConnection::JMedia2HTTPConnection(JNIEnv *env, jobject thiz) {
+ mMedia2HTTPConnectionObj = env->NewGlobalRef(thiz);
+ CHECK(mMedia2HTTPConnectionObj != NULL);
+
+ ScopedLocalRef<jclass> media2HTTPConnectionClass(
+ env, env->GetObjectClass(mMedia2HTTPConnectionObj));
+ CHECK(media2HTTPConnectionClass.get() != NULL);
+
+ mConnectMethod = env->GetMethodID(
+ media2HTTPConnectionClass.get(),
+ "connect",
+ "(Ljava/lang/String;Ljava/lang/String;)Z");
+ CHECK(mConnectMethod != NULL);
+
+ mDisconnectMethod = env->GetMethodID(
+ media2HTTPConnectionClass.get(),
+ "disconnect",
+ "()V");
+ CHECK(mDisconnectMethod != NULL);
+
+ mReadAtMethod = env->GetMethodID(
+ media2HTTPConnectionClass.get(),
+ "readAt",
+ "(J[BI)I");
+ CHECK(mReadAtMethod != NULL);
+
+ mGetSizeMethod = env->GetMethodID(
+ media2HTTPConnectionClass.get(),
+ "getSize",
+ "()J");
+ CHECK(mGetSizeMethod != NULL);
+
+ mGetMIMETypeMethod = env->GetMethodID(
+ media2HTTPConnectionClass.get(),
+ "getMIMEType",
+ "()Ljava/lang/String;");
+ CHECK(mGetMIMETypeMethod != NULL);
+
+ mGetUriMethod = env->GetMethodID(
+ media2HTTPConnectionClass.get(),
+ "getUri",
+ "()Ljava/lang/String;");
+ CHECK(mGetUriMethod != NULL);
+
+ ScopedLocalRef<jbyteArray> tmp(
+ env, env->NewByteArray(kBufferSize));
+ mByteArrayObj = (jbyteArray)env->NewGlobalRef(tmp.get());
+ CHECK(mByteArrayObj != NULL);
+}
+
+JMedia2HTTPConnection::~JMedia2HTTPConnection() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(mMedia2HTTPConnectionObj);
+ env->DeleteGlobalRef(mByteArrayObj);
+}
+
+bool JMedia2HTTPConnection::connect(
+ const char *uri, const KeyedVector<String8, String8> *headers) {
+ String8 tmp("");
+ if (headers != NULL) {
+ for (size_t i = 0; i < headers->size(); ++i) {
+ tmp.append(headers->keyAt(i));
+ tmp.append(String8(": "));
+ tmp.append(headers->valueAt(i));
+ tmp.append(String8("\r\n"));
+ }
+ }
+
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jstring juri = env->NewStringUTF(uri);
+ jstring jheaders = env->NewStringUTF(tmp.string());
+
+ jboolean ret =
+ env->CallBooleanMethod(mMedia2HTTPConnectionObj, mConnectMethod, juri, jheaders);
+
+ env->DeleteLocalRef(juri);
+ env->DeleteLocalRef(jheaders);
+
+ return (bool)ret;
+}
+
+void JMedia2HTTPConnection::disconnect() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->CallVoidMethod(mMedia2HTTPConnectionObj, mDisconnectMethod);
+}
+
+ssize_t JMedia2HTTPConnection::readAt(off64_t offset, void *data, size_t size) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+ if (size > kBufferSize) {
+ size = kBufferSize;
+ }
+
+ jint n = env->CallIntMethod(
+ mMedia2HTTPConnectionObj, mReadAtMethod, (jlong)offset, mByteArrayObj, (jint)size);
+
+ if (n > 0) {
+ env->GetByteArrayRegion(
+ mByteArrayObj,
+ 0,
+ n,
+ (jbyte *)data);
+ }
+
+ return n;
+}
+
+off64_t JMedia2HTTPConnection::getSize() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ return (off64_t)(env->CallLongMethod(mMedia2HTTPConnectionObj, mGetSizeMethod));
+}
+
+status_t JMedia2HTTPConnection::getMIMEType(String8 *mimeType) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jstring jmime = (jstring)env->CallObjectMethod(mMedia2HTTPConnectionObj, mGetMIMETypeMethod);
+ jboolean flag = env->ExceptionCheck();
+ if (flag) {
+ env->ExceptionClear();
+ return UNKNOWN_ERROR;
+ }
+
+ const char *str = env->GetStringUTFChars(jmime, 0);
+ if (str != NULL) {
+ *mimeType = String8(str);
+ } else {
+ *mimeType = "application/octet-stream";
+ }
+ env->ReleaseStringUTFChars(jmime, str);
+ return OK;
+}
+
+status_t JMedia2HTTPConnection::getUri(String8 *uri) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jstring juri = (jstring)env->CallObjectMethod(mMedia2HTTPConnectionObj, mGetUriMethod);
+ jboolean flag = env->ExceptionCheck();
+ if (flag) {
+ env->ExceptionClear();
+ return UNKNOWN_ERROR;
+ }
+
+ const char *str = env->GetStringUTFChars(juri, 0);
+ *uri = String8(str);
+ env->ReleaseStringUTFChars(juri, str);
+ return OK;
+}
+
+} // namespace android
diff --git a/media/jni/android_media_Media2HTTPConnection.h b/media/jni/android_media_Media2HTTPConnection.h
new file mode 100644
index 0000000..14bc677
--- /dev/null
+++ b/media/jni/android_media_Media2HTTPConnection.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017, 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.
+ */
+
+#ifndef _ANDROID_MEDIA_MEDIA2HTTPCONNECTION_H_
+#define _ANDROID_MEDIA_MEDIA2HTTPCONNECTION_H_
+
+#include "jni.h"
+
+#include <media/MediaHTTPConnection.h>
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+struct JMedia2HTTPConnection : public MediaHTTPConnection {
+ JMedia2HTTPConnection(JNIEnv *env, jobject thiz);
+
+ virtual bool connect(
+ const char *uri, const KeyedVector<String8, String8> *headers) override;
+
+ virtual void disconnect() override;
+ virtual ssize_t readAt(off64_t offset, void *data, size_t size) override;
+ virtual off64_t getSize() override;
+ virtual status_t getMIMEType(String8 *mimeType) override;
+ virtual status_t getUri(String8 *uri) override;
+
+protected:
+ virtual ~JMedia2HTTPConnection();
+
+private:
+ jobject mMedia2HTTPConnectionObj;
+ jmethodID mConnectMethod;
+ jmethodID mDisconnectMethod;
+ jmethodID mReadAtMethod;
+ jmethodID mGetSizeMethod;
+ jmethodID mGetMIMETypeMethod;
+ jmethodID mGetUriMethod;
+
+ jbyteArray mByteArrayObj;
+
+ DISALLOW_EVIL_CONSTRUCTORS(JMedia2HTTPConnection);
+};
+
+} // namespace android
+
+#endif // _ANDROID_MEDIA_MEDIA2HTTPCONNECTION_H_
diff --git a/media/jni/android_media_Media2HTTPService.cpp b/media/jni/android_media_Media2HTTPService.cpp
new file mode 100644
index 0000000..382f099
--- /dev/null
+++ b/media/jni/android_media_Media2HTTPService.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "Media2HTTPService-JNI"
+#include <utils/Log.h>
+
+#include "android_media_Media2HTTPConnection.h"
+#include "android_media_Media2HTTPService.h"
+
+#include "android_runtime/AndroidRuntime.h"
+#include "android_runtime/Log.h"
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+
+#include <media/stagefright/foundation/ADebug.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+namespace android {
+
+JMedia2HTTPService::JMedia2HTTPService(JNIEnv *env, jobject thiz) {
+ mMedia2HTTPServiceObj = env->NewGlobalRef(thiz);
+ CHECK(mMedia2HTTPServiceObj != NULL);
+
+ ScopedLocalRef<jclass> media2HTTPServiceClass(env, env->GetObjectClass(mMedia2HTTPServiceObj));
+ CHECK(media2HTTPServiceClass.get() != NULL);
+
+ mMakeHTTPConnectionMethod = env->GetMethodID(
+ media2HTTPServiceClass.get(),
+ "makeHTTPConnection",
+ "()Landroid/media/Media2HTTPConnection;");
+ CHECK(mMakeHTTPConnectionMethod != NULL);
+}
+
+JMedia2HTTPService::~JMedia2HTTPService() {
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(mMedia2HTTPServiceObj);
+}
+
+sp<MediaHTTPConnection> JMedia2HTTPService::makeHTTPConnection() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jobject media2HTTPConnectionObj =
+ env->CallObjectMethod(mMedia2HTTPServiceObj, mMakeHTTPConnectionMethod);
+
+ return new JMedia2HTTPConnection(env, media2HTTPConnectionObj);
+}
+
+} // namespace android
diff --git a/media/jni/android_media_Media2HTTPService.h b/media/jni/android_media_Media2HTTPService.h
new file mode 100644
index 0000000..30d03f5
--- /dev/null
+++ b/media/jni/android_media_Media2HTTPService.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017, 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.
+ */
+
+#ifndef _ANDROID_MEDIA_MEDIA2HTTPSERVICE_H_
+#define _ANDROID_MEDIA_MEDIA2HTTPSERVICE_H_
+
+#include "jni.h"
+
+#include <media/MediaHTTPService.h>
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+struct JMedia2HTTPService : public MediaHTTPService {
+ JMedia2HTTPService(JNIEnv *env, jobject thiz);
+
+ virtual sp<MediaHTTPConnection> makeHTTPConnection() override;
+
+protected:
+ virtual ~JMedia2HTTPService();
+
+private:
+ jobject mMedia2HTTPServiceObj;
+
+ jmethodID mMakeHTTPConnectionMethod;
+
+ DISALLOW_EVIL_CONSTRUCTORS(JMedia2HTTPService);
+};
+
+} // namespace android
+
+#endif // _ANDROID_MEDIA_MEDIA2HTTPSERVICE_H_
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 5c90d00..a855526 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -244,6 +244,10 @@
return mImpl->getSampleTime(sampleTimeUs);
}
+status_t JMediaExtractor::getSampleSize(size_t *sampleSize) {
+ return mImpl->getSampleSize(sampleSize);
+}
+
status_t JMediaExtractor::getSampleFlags(uint32_t *sampleFlags) {
*sampleFlags = 0;
@@ -505,6 +509,28 @@
return (jlong) sampleTimeUs;
}
+static jlong android_media_MediaExtractor_getSampleSize(
+ JNIEnv *env, jobject thiz) {
+ sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+ if (extractor == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return -1ll;
+ }
+
+ size_t sampleSize;
+ status_t err = extractor->getSampleSize(&sampleSize);
+
+ if (err == ERROR_END_OF_STREAM) {
+ return -1ll;
+ } else if (err != OK) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return -1ll;
+ }
+
+ return (jlong) sampleSize;
+}
+
static jint android_media_MediaExtractor_getSampleFlags(
JNIEnv *env, jobject thiz) {
sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
@@ -884,6 +910,9 @@
{ "getSampleTime", "()J",
(void *)android_media_MediaExtractor_getSampleTime },
+ { "getSampleSize", "()J",
+ (void *)android_media_MediaExtractor_getSampleSize },
+
{ "getSampleFlags", "()I",
(void *)android_media_MediaExtractor_getSampleFlags },
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
index a4638ac..aaa8421 100644
--- a/media/jni/android_media_MediaExtractor.h
+++ b/media/jni/android_media_MediaExtractor.h
@@ -60,6 +60,7 @@
status_t readSampleData(jobject byteBuf, size_t offset, size_t *sampleSize);
status_t getSampleTrackIndex(size_t *trackIndex);
status_t getSampleTime(int64_t *sampleTimeUs);
+ status_t getSampleSize(size_t *sampleSize);
status_t getSampleFlags(uint32_t *sampleFlags);
status_t getSampleMeta(sp<MetaData> *sampleMeta);
status_t getMetrics(Parcel *reply) const;
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
new file mode 100644
index 0000000..3bf0b37
--- /dev/null
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -0,0 +1,1514 @@
+/*
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaPlayer2-JNI"
+#include "utils/Log.h"
+
+#include <media/mediaplayer2.h>
+#include <media/AudioResamplerPublic.h>
+#include <media/MediaHTTPService.h>
+#include <media/MediaPlayer2Interface.h>
+#include <media/MediaAnalyticsItem.h>
+#include <media/NdkWrapper.h>
+#include <media/stagefright/foundation/ByteUtils.h> // for FOURCC definition
+#include <stdio.h>
+#include <assert.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <utils/threads.h>
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+#include "android/native_window_jni.h"
+#include "android_runtime/Log.h"
+#include "utils/Errors.h" // for status_t
+#include "utils/KeyedVector.h"
+#include "utils/String8.h"
+#include "android_media_BufferingParams.h"
+#include "android_media_Media2HTTPService.h"
+#include "android_media_Media2DataSource.h"
+#include "android_media_MediaMetricsJNI.h"
+#include "android_media_PlaybackParams.h"
+#include "android_media_SyncParams.h"
+#include "android_media_VolumeShaper.h"
+
+#include "android_os_Parcel.h"
+#include "android_util_Binder.h"
+#include <binder/Parcel.h>
+
+// Modular DRM begin
+#define FIND_CLASS(var, className) \
+var = env->FindClass(className); \
+LOG_FATAL_IF(! (var), "Unable to find class " className);
+
+#define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \
+var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \
+LOG_FATAL_IF(! (var), "Unable to find method " fieldName);
+
+struct StateExceptionFields {
+ jmethodID init;
+ jclass classId;
+};
+
+static StateExceptionFields gStateExceptionFields;
+// Modular DRM end
+
+// ----------------------------------------------------------------------------
+
+using namespace android;
+
+using media::VolumeShaper;
+
+// ----------------------------------------------------------------------------
+
+struct fields_t {
+ jfieldID context;
+ jfieldID surface_texture;
+
+ jmethodID post_event;
+
+ jmethodID proxyConfigGetHost;
+ jmethodID proxyConfigGetPort;
+ jmethodID proxyConfigGetExclusionList;
+};
+static fields_t fields;
+
+static BufferingParams::fields_t gBufferingParamsFields;
+static PlaybackParams::fields_t gPlaybackParamsFields;
+static SyncParams::fields_t gSyncParamsFields;
+static VolumeShaperHelper::fields_t gVolumeShaperFields;
+
+static Mutex sLock;
+
+static bool ConvertKeyValueArraysToKeyedVector(
+ JNIEnv *env, jobjectArray keys, jobjectArray values,
+ KeyedVector<String8, String8>* keyedVector) {
+
+ int nKeyValuePairs = 0;
+ bool failed = false;
+ if (keys != NULL && values != NULL) {
+ nKeyValuePairs = env->GetArrayLength(keys);
+ failed = (nKeyValuePairs != env->GetArrayLength(values));
+ }
+
+ if (!failed) {
+ failed = ((keys != NULL && values == NULL) ||
+ (keys == NULL && values != NULL));
+ }
+
+ if (failed) {
+ ALOGE("keys and values arrays have different length");
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return false;
+ }
+
+ for (int i = 0; i < nKeyValuePairs; ++i) {
+ // No need to check on the ArrayIndexOutOfBoundsException, since
+ // it won't happen here.
+ jstring key = (jstring) env->GetObjectArrayElement(keys, i);
+ jstring value = (jstring) env->GetObjectArrayElement(values, i);
+
+ const char* keyStr = env->GetStringUTFChars(key, NULL);
+ if (!keyStr) { // OutOfMemoryError
+ return false;
+ }
+
+ const char* valueStr = env->GetStringUTFChars(value, NULL);
+ if (!valueStr) { // OutOfMemoryError
+ env->ReleaseStringUTFChars(key, keyStr);
+ return false;
+ }
+
+ keyedVector->add(String8(keyStr), String8(valueStr));
+
+ env->ReleaseStringUTFChars(key, keyStr);
+ env->ReleaseStringUTFChars(value, valueStr);
+ env->DeleteLocalRef(key);
+ env->DeleteLocalRef(value);
+ }
+ return true;
+}
+
+// ----------------------------------------------------------------------------
+// ref-counted object for callbacks
+class JNIMediaPlayer2Listener: public MediaPlayer2Listener
+{
+public:
+ JNIMediaPlayer2Listener(JNIEnv* env, jobject thiz, jobject weak_thiz);
+ ~JNIMediaPlayer2Listener();
+ virtual void notify(int msg, int ext1, int ext2, const Parcel *obj = NULL);
+private:
+ JNIMediaPlayer2Listener();
+ jclass mClass; // Reference to MediaPlayer2 class
+ jobject mObject; // Weak ref to MediaPlayer2 Java object to call on
+};
+
+JNIMediaPlayer2Listener::JNIMediaPlayer2Listener(JNIEnv* env, jobject thiz, jobject weak_thiz)
+{
+
+ // Hold onto the MediaPlayer2 class for use in calling the static method
+ // that posts events to the application thread.
+ jclass clazz = env->GetObjectClass(thiz);
+ if (clazz == NULL) {
+ ALOGE("Can't find android/media/MediaPlayer2Impl");
+ jniThrowException(env, "java/lang/Exception", NULL);
+ return;
+ }
+ mClass = (jclass)env->NewGlobalRef(clazz);
+
+ // We use a weak reference so the MediaPlayer2 object can be garbage collected.
+ // The reference is only used as a proxy for callbacks.
+ mObject = env->NewGlobalRef(weak_thiz);
+}
+
+JNIMediaPlayer2Listener::~JNIMediaPlayer2Listener()
+{
+ // remove global references
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(mObject);
+ env->DeleteGlobalRef(mClass);
+}
+
+void JNIMediaPlayer2Listener::notify(int msg, int ext1, int ext2, const Parcel *obj)
+{
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
+ if (obj && obj->dataSize() > 0) {
+ jobject jParcel = createJavaParcelObject(env);
+ if (jParcel != NULL) {
+ Parcel* nativeParcel = parcelForJavaObject(env, jParcel);
+ nativeParcel->setData(obj->data(), obj->dataSize());
+ env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
+ msg, ext1, ext2, jParcel);
+ env->DeleteLocalRef(jParcel);
+ }
+ } else {
+ env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
+ msg, ext1, ext2, NULL);
+ }
+ if (env->ExceptionCheck()) {
+ ALOGW("An exception occurred while notifying an event.");
+ LOGW_EX(env);
+ env->ExceptionClear();
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+static sp<MediaPlayer2> getMediaPlayer(JNIEnv* env, jobject thiz)
+{
+ Mutex::Autolock l(sLock);
+ MediaPlayer2* const p = (MediaPlayer2*)env->GetLongField(thiz, fields.context);
+ return sp<MediaPlayer2>(p);
+}
+
+static sp<MediaPlayer2> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer2>& player)
+{
+ Mutex::Autolock l(sLock);
+ sp<MediaPlayer2> old = (MediaPlayer2*)env->GetLongField(thiz, fields.context);
+ if (player.get()) {
+ player->incStrong((void*)setMediaPlayer);
+ }
+ if (old != 0) {
+ old->decStrong((void*)setMediaPlayer);
+ }
+ env->SetLongField(thiz, fields.context, (jlong)player.get());
+ return old;
+}
+
+// If exception is NULL and opStatus is not OK, this method sends an error
+// event to the client application; otherwise, if exception is not NULL and
+// opStatus is not OK, this method throws the given exception to the client
+// application.
+static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message)
+{
+ if (exception == NULL) { // Don't throw exception. Instead, send an event.
+ if (opStatus != (status_t) OK) {
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp != 0) mp->notify(MEDIA2_ERROR, opStatus, 0);
+ }
+ } else { // Throw exception!
+ if ( opStatus == (status_t) INVALID_OPERATION ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ } else if ( opStatus == (status_t) BAD_VALUE ) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ } else if ( opStatus == (status_t) PERMISSION_DENIED ) {
+ jniThrowException(env, "java/lang/SecurityException", NULL);
+ } else if ( opStatus != (status_t) OK ) {
+ if (strlen(message) > 230) {
+ // if the message is too long, don't bother displaying the status code
+ jniThrowException( env, exception, message);
+ } else {
+ char msg[256];
+ // append the status code to the message
+ sprintf(msg, "%s: status=0x%X", message, opStatus);
+ jniThrowException( env, exception, msg);
+ }
+ }
+ }
+}
+
+static void
+android_media_MediaPlayer2_setDataSourceAndHeaders(
+ JNIEnv *env, jobject thiz, jobject httpServiceObj, jstring path,
+ jobjectArray keys, jobjectArray values) {
+
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (path == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ const char *tmp = env->GetStringUTFChars(path, NULL);
+ if (tmp == NULL) { // Out of memory
+ return;
+ }
+ ALOGV("setDataSource: path %s", tmp);
+
+ String8 pathStr(tmp);
+ env->ReleaseStringUTFChars(path, tmp);
+ tmp = NULL;
+
+ // We build a KeyedVector out of the key and val arrays
+ KeyedVector<String8, String8> headersVector;
+ if (!ConvertKeyValueArraysToKeyedVector(
+ env, keys, values, &headersVector)) {
+ return;
+ }
+
+ sp<MediaHTTPService> httpService;
+ if (httpServiceObj != NULL) {
+ httpService = new JMedia2HTTPService(env, httpServiceObj);
+ }
+
+ status_t opStatus =
+ mp->setDataSource(
+ httpService,
+ pathStr,
+ headersVector.size() > 0? &headersVector : NULL);
+
+ process_media_player_call(
+ env, thiz, opStatus, "java/io/IOException",
+ "setDataSource failed." );
+}
+
+static void
+android_media_MediaPlayer2_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (fileDescriptor == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+ int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+ ALOGV("setDataSourceFD: fd %d", fd);
+ process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
+}
+
+static void
+android_media_MediaPlayer2_setDataSourceCallback(JNIEnv *env, jobject thiz, jobject dataSource)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (dataSource == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+ sp<DataSource> callbackDataSource = new JMedia2DataSource(env, dataSource);
+ process_media_player_call(env, thiz, mp->setDataSource(callbackDataSource), "java/lang/RuntimeException", "setDataSourceCallback failed." );
+}
+
+static sp<ANativeWindowWrapper>
+getVideoSurfaceTexture(JNIEnv* env, jobject thiz) {
+ ANativeWindow * const p = (ANativeWindow*)env->GetLongField(thiz, fields.surface_texture);
+ return new ANativeWindowWrapper(p);
+}
+
+static void
+decVideoSurfaceRef(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ return;
+ }
+
+ ANativeWindow * const old_anw = (ANativeWindow*)env->GetLongField(thiz, fields.surface_texture);
+ if (old_anw != NULL) {
+ ANativeWindow_release(old_anw);
+ env->SetLongField(thiz, fields.surface_texture, (jlong)NULL);
+ }
+}
+
+static void
+setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface, jboolean mediaPlayerMustBeAlive)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ if (mediaPlayerMustBeAlive) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ }
+ return;
+ }
+
+ decVideoSurfaceRef(env, thiz);
+
+ ANativeWindow* anw = NULL;
+ if (jsurface) {
+ anw = ANativeWindow_fromSurface(env, jsurface);
+ if (anw == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "The surface has been released");
+ return;
+ }
+ }
+
+ env->SetLongField(thiz, fields.surface_texture, (jlong)anw);
+
+ // This will fail if the media player has not been initialized yet. This
+ // can be the case if setDisplay() on MediaPlayer2Impl.java has been called
+ // before setDataSource(). The redundant call to setVideoSurfaceTexture()
+ // in prepare/prepareAsync covers for this case.
+ mp->setVideoSurfaceTexture(new ANativeWindowWrapper(anw));
+}
+
+static void
+android_media_MediaPlayer2_setVideoSurface(JNIEnv *env, jobject thiz, jobject jsurface)
+{
+ setVideoSurface(env, thiz, jsurface, true /* mediaPlayerMustBeAlive */);
+}
+
+static jobject
+android_media_MediaPlayer2_getBufferingParams(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ BufferingParams bp;
+ BufferingSettings &settings = bp.settings;
+ process_media_player_call(
+ env, thiz, mp->getBufferingSettings(&settings),
+ "java/lang/IllegalStateException", "unexpected error");
+ ALOGV("getBufferingSettings:{%s}", settings.toString().string());
+
+ return bp.asJobject(env, gBufferingParamsFields);
+}
+
+static void
+android_media_MediaPlayer2_setBufferingParams(JNIEnv *env, jobject thiz, jobject params)
+{
+ if (params == NULL) {
+ return;
+ }
+
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ BufferingParams bp;
+ bp.fillFromJobject(env, gBufferingParamsFields, params);
+ ALOGV("setBufferingParams:{%s}", bp.settings.toString().string());
+
+ process_media_player_call(
+ env, thiz, mp->setBufferingSettings(bp.settings),
+ "java/lang/IllegalStateException", "unexpected error");
+}
+
+static void
+android_media_MediaPlayer2_prepare(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ // Handle the case where the display surface was set before the mp was
+ // initialized. We try again to make it stick.
+ sp<ANativeWindowWrapper> st = getVideoSurfaceTexture(env, thiz);
+ mp->setVideoSurfaceTexture(st);
+
+ process_media_player_call( env, thiz, mp->prepare(), "java/io/IOException", "Prepare failed." );
+}
+
+static void
+android_media_MediaPlayer2_prepareAsync(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ // Handle the case where the display surface was set before the mp was
+ // initialized. We try again to make it stick.
+ sp<ANativeWindowWrapper> st = getVideoSurfaceTexture(env, thiz);
+ mp->setVideoSurfaceTexture(st);
+
+ process_media_player_call( env, thiz, mp->prepareAsync(), "java/io/IOException", "Prepare Async failed." );
+}
+
+static void
+android_media_MediaPlayer2_start(JNIEnv *env, jobject thiz)
+{
+ ALOGV("start");
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ process_media_player_call( env, thiz, mp->start(), NULL, NULL );
+}
+
+static void
+android_media_MediaPlayer2_stop(JNIEnv *env, jobject thiz)
+{
+ ALOGV("stop");
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ process_media_player_call( env, thiz, mp->stop(), NULL, NULL );
+}
+
+static void
+android_media_MediaPlayer2_pause(JNIEnv *env, jobject thiz)
+{
+ ALOGV("pause");
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ process_media_player_call( env, thiz, mp->pause(), NULL, NULL );
+}
+
+static jboolean
+android_media_MediaPlayer2_isPlaying(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return JNI_FALSE;
+ }
+ const jboolean is_playing = mp->isPlaying();
+
+ ALOGV("isPlaying: %d", is_playing);
+ return is_playing;
+}
+
+static void
+android_media_MediaPlayer2_setPlaybackParams(JNIEnv *env, jobject thiz, jobject params)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ PlaybackParams pbp;
+ pbp.fillFromJobject(env, gPlaybackParamsFields, params);
+ ALOGV("setPlaybackParams: %d:%f %d:%f %d:%u %d:%u",
+ pbp.speedSet, pbp.audioRate.mSpeed,
+ pbp.pitchSet, pbp.audioRate.mPitch,
+ pbp.audioFallbackModeSet, pbp.audioRate.mFallbackMode,
+ pbp.audioStretchModeSet, pbp.audioRate.mStretchMode);
+
+ AudioPlaybackRate rate;
+ status_t err = mp->getPlaybackSettings(&rate);
+ if (err == OK) {
+ bool updatedRate = false;
+ if (pbp.speedSet) {
+ rate.mSpeed = pbp.audioRate.mSpeed;
+ updatedRate = true;
+ }
+ if (pbp.pitchSet) {
+ rate.mPitch = pbp.audioRate.mPitch;
+ updatedRate = true;
+ }
+ if (pbp.audioFallbackModeSet) {
+ rate.mFallbackMode = pbp.audioRate.mFallbackMode;
+ updatedRate = true;
+ }
+ if (pbp.audioStretchModeSet) {
+ rate.mStretchMode = pbp.audioRate.mStretchMode;
+ updatedRate = true;
+ }
+ if (updatedRate) {
+ err = mp->setPlaybackSettings(rate);
+ }
+ }
+ process_media_player_call(
+ env, thiz, err,
+ "java/lang/IllegalStateException", "unexpected error");
+}
+
+static jobject
+android_media_MediaPlayer2_getPlaybackParams(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ PlaybackParams pbp;
+ AudioPlaybackRate &audioRate = pbp.audioRate;
+ process_media_player_call(
+ env, thiz, mp->getPlaybackSettings(&audioRate),
+ "java/lang/IllegalStateException", "unexpected error");
+ ALOGV("getPlaybackSettings: %f %f %d %d",
+ audioRate.mSpeed, audioRate.mPitch, audioRate.mFallbackMode, audioRate.mStretchMode);
+
+ pbp.speedSet = true;
+ pbp.pitchSet = true;
+ pbp.audioFallbackModeSet = true;
+ pbp.audioStretchModeSet = true;
+
+ return pbp.asJobject(env, gPlaybackParamsFields);
+}
+
+static void
+android_media_MediaPlayer2_setSyncParams(JNIEnv *env, jobject thiz, jobject params)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ SyncParams scp;
+ scp.fillFromJobject(env, gSyncParamsFields, params);
+ ALOGV("setSyncParams: %d:%d %d:%d %d:%f %d:%f",
+ scp.syncSourceSet, scp.sync.mSource,
+ scp.audioAdjustModeSet, scp.sync.mAudioAdjustMode,
+ scp.toleranceSet, scp.sync.mTolerance,
+ scp.frameRateSet, scp.frameRate);
+
+ AVSyncSettings avsync;
+ float videoFrameRate;
+ status_t err = mp->getSyncSettings(&avsync, &videoFrameRate);
+ if (err == OK) {
+ bool updatedSync = scp.frameRateSet;
+ if (scp.syncSourceSet) {
+ avsync.mSource = scp.sync.mSource;
+ updatedSync = true;
+ }
+ if (scp.audioAdjustModeSet) {
+ avsync.mAudioAdjustMode = scp.sync.mAudioAdjustMode;
+ updatedSync = true;
+ }
+ if (scp.toleranceSet) {
+ avsync.mTolerance = scp.sync.mTolerance;
+ updatedSync = true;
+ }
+ if (updatedSync) {
+ err = mp->setSyncSettings(avsync, scp.frameRateSet ? scp.frameRate : -1.f);
+ }
+ }
+ process_media_player_call(
+ env, thiz, err,
+ "java/lang/IllegalStateException", "unexpected error");
+}
+
+static jobject
+android_media_MediaPlayer2_getSyncParams(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ SyncParams scp;
+ scp.frameRate = -1.f;
+ process_media_player_call(
+ env, thiz, mp->getSyncSettings(&scp.sync, &scp.frameRate),
+ "java/lang/IllegalStateException", "unexpected error");
+
+ ALOGV("getSyncSettings: %d %d %f %f",
+ scp.sync.mSource, scp.sync.mAudioAdjustMode, scp.sync.mTolerance, scp.frameRate);
+
+ // sanity check params
+ if (scp.sync.mSource >= AVSYNC_SOURCE_MAX
+ || scp.sync.mAudioAdjustMode >= AVSYNC_AUDIO_ADJUST_MODE_MAX
+ || scp.sync.mTolerance < 0.f
+ || scp.sync.mTolerance >= AVSYNC_TOLERANCE_MAX) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ scp.syncSourceSet = true;
+ scp.audioAdjustModeSet = true;
+ scp.toleranceSet = true;
+ scp.frameRateSet = scp.frameRate >= 0.f;
+
+ return scp.asJobject(env, gSyncParamsFields);
+}
+
+static void
+android_media_MediaPlayer2_seekTo(JNIEnv *env, jobject thiz, jlong msec, jint mode)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ ALOGV("seekTo: %lld(msec), mode=%d", (long long)msec, mode);
+ process_media_player_call( env, thiz, mp->seekTo((int)msec, (MediaPlayer2SeekMode)mode), NULL, NULL );
+}
+
+static void
+android_media_MediaPlayer2_notifyAt(JNIEnv *env, jobject thiz, jlong mediaTimeUs)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ ALOGV("notifyAt: %lld", (long long)mediaTimeUs);
+ process_media_player_call( env, thiz, mp->notifyAt((int64_t)mediaTimeUs), NULL, NULL );
+}
+
+static jint
+android_media_MediaPlayer2_getVideoWidth(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+ int w;
+ if (0 != mp->getVideoWidth(&w)) {
+ ALOGE("getVideoWidth failed");
+ w = 0;
+ }
+ ALOGV("getVideoWidth: %d", w);
+ return (jint) w;
+}
+
+static jint
+android_media_MediaPlayer2_getVideoHeight(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+ int h;
+ if (0 != mp->getVideoHeight(&h)) {
+ ALOGE("getVideoHeight failed");
+ h = 0;
+ }
+ ALOGV("getVideoHeight: %d", h);
+ return (jint) h;
+}
+
+static jobject
+android_media_MediaPlayer2_native_getMetrics(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+
+ Parcel p;
+ int key = FOURCC('m','t','r','X');
+ status_t status = mp->getParameter(key, &p);
+ if (status != OK) {
+ ALOGD("getMetrics() failed: %d", status);
+ return (jobject) NULL;
+ }
+
+ p.setDataPosition(0);
+ MediaAnalyticsItem *item = new MediaAnalyticsItem;
+ item->readFromParcel(p);
+ jobject mybundle = MediaMetricsJNI::writeMetricsToBundle(env, item, NULL);
+
+ // housekeeping
+ delete item;
+ item = NULL;
+
+ return mybundle;
+}
+
+static jint
+android_media_MediaPlayer2_getCurrentPosition(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+ int msec;
+ process_media_player_call( env, thiz, mp->getCurrentPosition(&msec), NULL, NULL );
+ ALOGV("getCurrentPosition: %d (msec)", msec);
+ return (jint) msec;
+}
+
+static jint
+android_media_MediaPlayer2_getDuration(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+ int msec;
+ process_media_player_call( env, thiz, mp->getDuration(&msec), NULL, NULL );
+ ALOGV("getDuration: %d (msec)", msec);
+ return (jint) msec;
+}
+
+static void
+android_media_MediaPlayer2_reset(JNIEnv *env, jobject thiz)
+{
+ ALOGV("reset");
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ process_media_player_call( env, thiz, mp->reset(), NULL, NULL );
+}
+
+static jint
+android_media_MediaPlayer2_getAudioStreamType(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+ audio_stream_type_t streamtype;
+ process_media_player_call( env, thiz, mp->getAudioStreamType(&streamtype), NULL, NULL );
+ ALOGV("getAudioStreamType: %d (streamtype)", streamtype);
+ return (jint) streamtype;
+}
+
+static jboolean
+android_media_MediaPlayer2_setParameter(JNIEnv *env, jobject thiz, jint key, jobject java_request)
+{
+ ALOGV("setParameter: key %d", key);
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return false;
+ }
+
+ Parcel *request = parcelForJavaObject(env, java_request);
+ status_t err = mp->setParameter(key, *request);
+ if (err == OK) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static void
+android_media_MediaPlayer2_setLooping(JNIEnv *env, jobject thiz, jboolean looping)
+{
+ ALOGV("setLooping: %d", looping);
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ process_media_player_call( env, thiz, mp->setLooping(looping), NULL, NULL );
+}
+
+static jboolean
+android_media_MediaPlayer2_isLooping(JNIEnv *env, jobject thiz)
+{
+ ALOGV("isLooping");
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return JNI_FALSE;
+ }
+ return mp->isLooping() ? JNI_TRUE : JNI_FALSE;
+}
+
+static void
+android_media_MediaPlayer2_setVolume(JNIEnv *env, jobject thiz, jfloat leftVolume, jfloat rightVolume)
+{
+ ALOGV("setVolume: left %f right %f", (float) leftVolume, (float) rightVolume);
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ process_media_player_call( env, thiz, mp->setVolume((float) leftVolume, (float) rightVolume), NULL, NULL );
+}
+
+// Sends the request and reply parcels to the media player via the
+// binder interface.
+static jint
+android_media_MediaPlayer2_invoke(JNIEnv *env, jobject thiz,
+ jobject java_request, jobject java_reply)
+{
+ sp<MediaPlayer2> media_player = getMediaPlayer(env, thiz);
+ if (media_player == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return UNKNOWN_ERROR;
+ }
+
+ Parcel *request = parcelForJavaObject(env, java_request);
+ Parcel *reply = parcelForJavaObject(env, java_reply);
+
+ request->setDataPosition(0);
+
+ // Don't use process_media_player_call which use the async loop to
+ // report errors, instead returns the status.
+ return (jint) media_player->invoke(*request, reply);
+}
+
+// Sends the new filter to the client.
+static jint
+android_media_MediaPlayer2_setMetadataFilter(JNIEnv *env, jobject thiz, jobject request)
+{
+ sp<MediaPlayer2> media_player = getMediaPlayer(env, thiz);
+ if (media_player == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return UNKNOWN_ERROR;
+ }
+
+ Parcel *filter = parcelForJavaObject(env, request);
+
+ if (filter == NULL ) {
+ jniThrowException(env, "java/lang/RuntimeException", "Filter is null");
+ return UNKNOWN_ERROR;
+ }
+
+ return (jint) media_player->setMetadataFilter(*filter);
+}
+
+static jboolean
+android_media_MediaPlayer2_getMetadata(JNIEnv *env, jobject thiz, jboolean update_only,
+ jboolean apply_filter, jobject reply)
+{
+ sp<MediaPlayer2> media_player = getMediaPlayer(env, thiz);
+ if (media_player == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return JNI_FALSE;
+ }
+
+ Parcel *metadata = parcelForJavaObject(env, reply);
+
+ if (metadata == NULL ) {
+ jniThrowException(env, "java/lang/RuntimeException", "Reply parcel is null");
+ return JNI_FALSE;
+ }
+
+ metadata->freeData();
+ // On return metadata is positioned at the beginning of the
+ // metadata. Note however that the parcel actually starts with the
+ // return code so you should not rewind the parcel using
+ // setDataPosition(0).
+ if (media_player->getMetadata(update_only, apply_filter, metadata) == OK) {
+ return JNI_TRUE;
+ } else {
+ return JNI_FALSE;
+ }
+}
+
+// This function gets some field IDs, which in turn causes class initialization.
+// It is called from a static block in MediaPlayer2, which won't run until the
+// first time an instance of this class is used.
+static void
+android_media_MediaPlayer2_native_init(JNIEnv *env)
+{
+ jclass clazz;
+
+ clazz = env->FindClass("android/media/MediaPlayer2Impl");
+ if (clazz == NULL) {
+ return;
+ }
+
+ fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
+ if (fields.context == NULL) {
+ return;
+ }
+
+ fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
+ "(Ljava/lang/Object;IIILjava/lang/Object;)V");
+ if (fields.post_event == NULL) {
+ return;
+ }
+
+ fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");
+ if (fields.surface_texture == NULL) {
+ return;
+ }
+
+ env->DeleteLocalRef(clazz);
+
+ clazz = env->FindClass("android/net/ProxyInfo");
+ if (clazz == NULL) {
+ return;
+ }
+
+ fields.proxyConfigGetHost =
+ env->GetMethodID(clazz, "getHost", "()Ljava/lang/String;");
+
+ fields.proxyConfigGetPort =
+ env->GetMethodID(clazz, "getPort", "()I");
+
+ fields.proxyConfigGetExclusionList =
+ env->GetMethodID(clazz, "getExclusionListAsString", "()Ljava/lang/String;");
+
+ env->DeleteLocalRef(clazz);
+
+ gBufferingParamsFields.init(env);
+
+ // Modular DRM
+ FIND_CLASS(clazz, "android/media/MediaDrm$MediaDrmStateException");
+ if (clazz) {
+ GET_METHOD_ID(gStateExceptionFields.init, clazz, "<init>", "(ILjava/lang/String;)V");
+ gStateExceptionFields.classId = static_cast<jclass>(env->NewGlobalRef(clazz));
+
+ env->DeleteLocalRef(clazz);
+ } else {
+ ALOGE("JNI android_media_MediaPlayer2_native_init couldn't "
+ "get clazz android/media/MediaDrm$MediaDrmStateException");
+ }
+
+ gPlaybackParamsFields.init(env);
+ gSyncParamsFields.init(env);
+ gVolumeShaperFields.init(env);
+}
+
+static void
+android_media_MediaPlayer2_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
+{
+ ALOGV("native_setup");
+ sp<MediaPlayer2> mp = new MediaPlayer2();
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
+ return;
+ }
+
+ // create new listener and give it to MediaPlayer2
+ sp<JNIMediaPlayer2Listener> listener = new JNIMediaPlayer2Listener(env, thiz, weak_this);
+ mp->setListener(listener);
+
+ // Stow our new C++ MediaPlayer2 in an opaque field in the Java object.
+ setMediaPlayer(env, thiz, mp);
+}
+
+static void
+android_media_MediaPlayer2_release(JNIEnv *env, jobject thiz)
+{
+ ALOGV("release");
+ decVideoSurfaceRef(env, thiz);
+ sp<MediaPlayer2> mp = setMediaPlayer(env, thiz, 0);
+ if (mp != NULL) {
+ // this prevents native callbacks after the object is released
+ mp->setListener(0);
+ mp->disconnect();
+ }
+}
+
+static void
+android_media_MediaPlayer2_native_finalize(JNIEnv *env, jobject thiz)
+{
+ ALOGV("native_finalize");
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp != NULL) {
+ ALOGW("MediaPlayer2 finalized without being released");
+ }
+ android_media_MediaPlayer2_release(env, thiz);
+}
+
+static void android_media_MediaPlayer2_set_audio_session_id(JNIEnv *env, jobject thiz,
+ jint sessionId) {
+ ALOGV("set_session_id(): %d", sessionId);
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ process_media_player_call( env, thiz, mp->setAudioSessionId((audio_session_t) sessionId), NULL,
+ NULL);
+}
+
+static jint android_media_MediaPlayer2_get_audio_session_id(JNIEnv *env, jobject thiz) {
+ ALOGV("get_session_id()");
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return 0;
+ }
+
+ return (jint) mp->getAudioSessionId();
+}
+
+static void
+android_media_MediaPlayer2_setAuxEffectSendLevel(JNIEnv *env, jobject thiz, jfloat level)
+{
+ ALOGV("setAuxEffectSendLevel: level %f", level);
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ process_media_player_call( env, thiz, mp->setAuxEffectSendLevel(level), NULL, NULL );
+}
+
+static void android_media_MediaPlayer2_attachAuxEffect(JNIEnv *env, jobject thiz, jint effectId) {
+ ALOGV("attachAuxEffect(): %d", effectId);
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+ process_media_player_call( env, thiz, mp->attachAuxEffect(effectId), NULL, NULL );
+}
+
+static jint
+android_media_MediaPlayer2_setRetransmitEndpoint(JNIEnv *env, jobject thiz,
+ jstring addrString, jint port) {
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return INVALID_OPERATION;
+ }
+
+ const char *cAddrString = NULL;
+
+ if (NULL != addrString) {
+ cAddrString = env->GetStringUTFChars(addrString, NULL);
+ if (cAddrString == NULL) { // Out of memory
+ return NO_MEMORY;
+ }
+ }
+ ALOGV("setRetransmitEndpoint: %s:%d",
+ cAddrString ? cAddrString : "(null)", port);
+
+ status_t ret;
+ if (cAddrString && (port > 0xFFFF)) {
+ ret = BAD_VALUE;
+ } else {
+ ret = mp->setRetransmitEndpoint(cAddrString,
+ static_cast<uint16_t>(port));
+ }
+
+ if (NULL != addrString) {
+ env->ReleaseStringUTFChars(addrString, cAddrString);
+ }
+
+ if (ret == INVALID_OPERATION ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ }
+
+ return (jint) ret;
+}
+
+static void
+android_media_MediaPlayer2_setNextMediaPlayer(JNIEnv *env, jobject thiz, jobject java_player)
+{
+ ALOGV("setNextMediaPlayer");
+ sp<MediaPlayer2> thisplayer = getMediaPlayer(env, thiz);
+ if (thisplayer == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", "This player not initialized");
+ return;
+ }
+ sp<MediaPlayer2> nextplayer = (java_player == NULL) ? NULL : getMediaPlayer(env, java_player);
+ if (nextplayer == NULL && java_player != NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", "That player not initialized");
+ return;
+ }
+
+ if (nextplayer == thisplayer) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Next player can't be self");
+ return;
+ }
+ // tie the two players together
+ process_media_player_call(
+ env, thiz, thisplayer->setNextMediaPlayer(nextplayer),
+ "java/lang/IllegalArgumentException",
+ "setNextMediaPlayer failed." );
+ ;
+}
+
+// Pass through the arguments to the MediaServer player implementation.
+static jint android_media_MediaPlayer2_applyVolumeShaper(JNIEnv *env, jobject thiz,
+ jobject jconfig, jobject joperation) {
+ // NOTE: hard code here to prevent platform issues. Must match VolumeShaper.java
+ const int VOLUME_SHAPER_INVALID_OPERATION = -38;
+
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == nullptr) {
+ return (jint)VOLUME_SHAPER_INVALID_OPERATION;
+ }
+
+ sp<VolumeShaper::Configuration> configuration;
+ sp<VolumeShaper::Operation> operation;
+ if (jconfig != nullptr) {
+ configuration = VolumeShaperHelper::convertJobjectToConfiguration(
+ env, gVolumeShaperFields, jconfig);
+ ALOGV("applyVolumeShaper configuration: %s", configuration->toString().c_str());
+ }
+ if (joperation != nullptr) {
+ operation = VolumeShaperHelper::convertJobjectToOperation(
+ env, gVolumeShaperFields, joperation);
+ ALOGV("applyVolumeShaper operation: %s", operation->toString().c_str());
+ }
+ VolumeShaper::Status status = mp->applyVolumeShaper(configuration, operation);
+ if (status == INVALID_OPERATION) {
+ status = VOLUME_SHAPER_INVALID_OPERATION;
+ }
+ return (jint)status; // if status < 0 an error, else a VolumeShaper id
+}
+
+// Pass through the arguments to the MediaServer player implementation.
+static jobject android_media_MediaPlayer2_getVolumeShaperState(JNIEnv *env, jobject thiz,
+ jint id) {
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == nullptr) {
+ return (jobject)nullptr;
+ }
+
+ sp<VolumeShaper::State> state = mp->getVolumeShaperState((int)id);
+ if (state.get() == nullptr) {
+ return (jobject)nullptr;
+ }
+ return VolumeShaperHelper::convertStateToJobject(env, gVolumeShaperFields, state);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////
+// Modular DRM begin
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static void throwDrmStateException(JNIEnv *env, const char *msg, status_t err)
+{
+ ALOGE("Illegal DRM state exception: %s (%d)", msg, err);
+
+ jobject exception = env->NewObject(gStateExceptionFields.classId,
+ gStateExceptionFields.init, static_cast<int>(err),
+ env->NewStringUTF(msg));
+ env->Throw(static_cast<jthrowable>(exception));
+}
+
+// TODO: investigate if these can be shared with their MediaDrm counterparts
+static bool throwDrmExceptionAsNecessary(JNIEnv *env, status_t err, const char *msg = NULL)
+{
+ const char *drmMessage = "Unknown DRM Msg";
+
+ switch (err) {
+ case ERROR_DRM_UNKNOWN:
+ drmMessage = "General DRM error";
+ break;
+ case ERROR_DRM_NO_LICENSE:
+ drmMessage = "No license";
+ break;
+ case ERROR_DRM_LICENSE_EXPIRED:
+ drmMessage = "License expired";
+ break;
+ case ERROR_DRM_SESSION_NOT_OPENED:
+ drmMessage = "Session not opened";
+ break;
+ case ERROR_DRM_DECRYPT_UNIT_NOT_INITIALIZED:
+ drmMessage = "Not initialized";
+ break;
+ case ERROR_DRM_DECRYPT:
+ drmMessage = "Decrypt error";
+ break;
+ case ERROR_DRM_CANNOT_HANDLE:
+ drmMessage = "Unsupported scheme or data format";
+ break;
+ case ERROR_DRM_TAMPER_DETECTED:
+ drmMessage = "Invalid state";
+ break;
+ default:
+ break;
+ }
+
+ String8 vendorMessage;
+ if (err >= ERROR_DRM_VENDOR_MIN && err <= ERROR_DRM_VENDOR_MAX) {
+ vendorMessage = String8::format("DRM vendor-defined error: %d", err);
+ drmMessage = vendorMessage.string();
+ }
+
+ if (err == BAD_VALUE) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", msg);
+ return true;
+ } else if (err == ERROR_DRM_NOT_PROVISIONED) {
+ jniThrowException(env, "android/media/NotProvisionedException", msg);
+ return true;
+ } else if (err == ERROR_DRM_RESOURCE_BUSY) {
+ jniThrowException(env, "android/media/ResourceBusyException", msg);
+ return true;
+ } else if (err == ERROR_DRM_DEVICE_REVOKED) {
+ jniThrowException(env, "android/media/DeniedByServerException", msg);
+ return true;
+ } else if (err == DEAD_OBJECT) {
+ jniThrowException(env, "android/media/MediaDrmResetException",
+ "mediaserver died");
+ return true;
+ } else if (err != OK) {
+ String8 errbuf;
+ if (drmMessage != NULL) {
+ if (msg == NULL) {
+ msg = drmMessage;
+ } else {
+ errbuf = String8::format("%s: %s", msg, drmMessage);
+ msg = errbuf.string();
+ }
+ }
+ throwDrmStateException(env, msg, err);
+ return true;
+ }
+ return false;
+}
+
+static Vector<uint8_t> JByteArrayToVector(JNIEnv *env, jbyteArray const &byteArray)
+{
+ Vector<uint8_t> vector;
+ size_t length = env->GetArrayLength(byteArray);
+ vector.insertAt((size_t)0, length);
+ env->GetByteArrayRegion(byteArray, 0, length, (jbyte *)vector.editArray());
+ return vector;
+}
+
+static void android_media_MediaPlayer2_prepareDrm(JNIEnv *env, jobject thiz,
+ jbyteArray uuidObj, jbyteArray drmSessionIdObj)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (uuidObj == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ Vector<uint8_t> uuid = JByteArrayToVector(env, uuidObj);
+
+ if (uuid.size() != 16) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalArgumentException",
+ "invalid UUID size, expected 16 bytes");
+ return;
+ }
+
+ Vector<uint8_t> drmSessionId = JByteArrayToVector(env, drmSessionIdObj);
+
+ if (drmSessionId.size() == 0) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalArgumentException",
+ "empty drmSessionId");
+ return;
+ }
+
+ status_t err = mp->prepareDrm(uuid.array(), drmSessionId);
+ if (err != OK) {
+ if (err == INVALID_OPERATION) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalStateException",
+ "The player must be in prepared state.");
+ } else if (err == ERROR_DRM_CANNOT_HANDLE) {
+ jniThrowException(
+ env,
+ "android/media/UnsupportedSchemeException",
+ "Failed to instantiate drm object.");
+ } else {
+ throwDrmExceptionAsNecessary(env, err, "Failed to prepare DRM scheme");
+ }
+ }
+}
+
+static void android_media_MediaPlayer2_releaseDrm(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL ) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ status_t err = mp->releaseDrm();
+ if (err != OK) {
+ if (err == INVALID_OPERATION) {
+ jniThrowException(
+ env,
+ "java/lang/IllegalStateException",
+ "Can not release DRM in an active player state.");
+ }
+ }
+}
+// Modular DRM end
+// ----------------------------------------------------------------------------
+
+/////////////////////////////////////////////////////////////////////////////////////
+// AudioRouting begin
+static jboolean android_media_MediaPlayer2_setOutputDevice(JNIEnv *env, jobject thiz, jint device_id)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ return false;
+ }
+ return mp->setOutputDevice(device_id) == NO_ERROR;
+}
+
+static jint android_media_MediaPlayer2_getRoutedDeviceId(JNIEnv *env, jobject thiz)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ return AUDIO_PORT_HANDLE_NONE;
+ }
+ return mp->getRoutedDeviceId();
+}
+
+static void android_media_MediaPlayer2_enableDeviceCallback(
+ JNIEnv* env, jobject thiz, jboolean enabled)
+{
+ sp<MediaPlayer2> mp = getMediaPlayer(env, thiz);
+ if (mp == NULL) {
+ return;
+ }
+
+ status_t status = mp->enableAudioDeviceCallback(enabled);
+ if (status != NO_ERROR) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ ALOGE("enable device callback failed: %d", status);
+ }
+}
+
+// AudioRouting end
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gMethods[] = {
+ {
+ "nativeSetDataSource",
+ "(Landroid/media/Media2HTTPService;Ljava/lang/String;[Ljava/lang/String;"
+ "[Ljava/lang/String;)V",
+ (void *)android_media_MediaPlayer2_setDataSourceAndHeaders
+ },
+
+ {"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer2_setDataSourceFD},
+ {"_setDataSource", "(Landroid/media/Media2DataSource;)V",(void *)android_media_MediaPlayer2_setDataSourceCallback },
+ {"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer2_setVideoSurface},
+ {"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer2_getBufferingParams},
+ {"setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer2_setBufferingParams},
+ {"_prepare", "()V", (void *)android_media_MediaPlayer2_prepare},
+ {"prepareAsync", "()V", (void *)android_media_MediaPlayer2_prepareAsync},
+ {"_start", "()V", (void *)android_media_MediaPlayer2_start},
+ {"_stop", "()V", (void *)android_media_MediaPlayer2_stop},
+ {"getVideoWidth", "()I", (void *)android_media_MediaPlayer2_getVideoWidth},
+ {"getVideoHeight", "()I", (void *)android_media_MediaPlayer2_getVideoHeight},
+ {"native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_MediaPlayer2_native_getMetrics},
+ {"setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer2_setPlaybackParams},
+ {"getPlaybackParams", "()Landroid/media/PlaybackParams;", (void *)android_media_MediaPlayer2_getPlaybackParams},
+ {"setSyncParams", "(Landroid/media/SyncParams;)V", (void *)android_media_MediaPlayer2_setSyncParams},
+ {"getSyncParams", "()Landroid/media/SyncParams;", (void *)android_media_MediaPlayer2_getSyncParams},
+ {"_seekTo", "(JI)V", (void *)android_media_MediaPlayer2_seekTo},
+ {"_notifyAt", "(J)V", (void *)android_media_MediaPlayer2_notifyAt},
+ {"_pause", "()V", (void *)android_media_MediaPlayer2_pause},
+ {"isPlaying", "()Z", (void *)android_media_MediaPlayer2_isPlaying},
+ {"getCurrentPosition", "()I", (void *)android_media_MediaPlayer2_getCurrentPosition},
+ {"getDuration", "()I", (void *)android_media_MediaPlayer2_getDuration},
+ {"_release", "()V", (void *)android_media_MediaPlayer2_release},
+ {"_reset", "()V", (void *)android_media_MediaPlayer2_reset},
+ {"_getAudioStreamType", "()I", (void *)android_media_MediaPlayer2_getAudioStreamType},
+ {"setParameter", "(ILandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer2_setParameter},
+ {"setLooping", "(Z)V", (void *)android_media_MediaPlayer2_setLooping},
+ {"isLooping", "()Z", (void *)android_media_MediaPlayer2_isLooping},
+ {"_setVolume", "(FF)V", (void *)android_media_MediaPlayer2_setVolume},
+ {"native_invoke", "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer2_invoke},
+ {"native_setMetadataFilter", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer2_setMetadataFilter},
+ {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer2_getMetadata},
+ {"native_init", "()V", (void *)android_media_MediaPlayer2_native_init},
+ {"native_setup", "(Ljava/lang/Object;)V", (void *)android_media_MediaPlayer2_native_setup},
+ {"native_finalize", "()V", (void *)android_media_MediaPlayer2_native_finalize},
+ {"getAudioSessionId", "()I", (void *)android_media_MediaPlayer2_get_audio_session_id},
+ {"setAudioSessionId", "(I)V", (void *)android_media_MediaPlayer2_set_audio_session_id},
+ {"_setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer2_setAuxEffectSendLevel},
+ {"attachAuxEffect", "(I)V", (void *)android_media_MediaPlayer2_attachAuxEffect},
+ {"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer2_setRetransmitEndpoint},
+ {"setNextMediaPlayer", "(Landroid/media/MediaPlayer2;)V", (void *)android_media_MediaPlayer2_setNextMediaPlayer},
+ {"native_applyVolumeShaper",
+ "(Landroid/media/VolumeShaper$Configuration;Landroid/media/VolumeShaper$Operation;)I",
+ (void *)android_media_MediaPlayer2_applyVolumeShaper},
+ {"native_getVolumeShaperState",
+ "(I)Landroid/media/VolumeShaper$State;",
+ (void *)android_media_MediaPlayer2_getVolumeShaperState},
+ // Modular DRM
+ { "_prepareDrm", "([B[B)V", (void *)android_media_MediaPlayer2_prepareDrm },
+ { "_releaseDrm", "()V", (void *)android_media_MediaPlayer2_releaseDrm },
+
+ // AudioRouting
+ {"native_setOutputDevice", "(I)Z", (void *)android_media_MediaPlayer2_setOutputDevice},
+ {"native_getRoutedDeviceId", "()I", (void *)android_media_MediaPlayer2_getRoutedDeviceId},
+ {"native_enableDeviceCallback", "(Z)V", (void *)android_media_MediaPlayer2_enableDeviceCallback},
+};
+
+// This function only registers the native methods
+static int register_android_media_MediaPlayer2Impl(JNIEnv *env)
+{
+ return AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaPlayer2Impl", gMethods, NELEM(gMethods));
+}
+
+jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
+{
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+ if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ ALOGE("ERROR: GetEnv failed\n");
+ goto bail;
+ }
+ assert(env != NULL);
+
+ if (register_android_media_MediaPlayer2Impl(env) < 0) {
+ ALOGE("ERROR: MediaPlayer2 native registration failed\n");
+ goto bail;
+ }
+
+ /* success -- return valid version number */
+ result = JNI_VERSION_1_4;
+
+bail:
+ return result;
+}
+
+// KTHXBYE
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index cf5882f..d8c975f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -25,6 +25,7 @@
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -230,8 +231,8 @@
* android.hardware.camera2.CaptureResultExtras)
*/
@Override
- public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras)
- throws RemoteException {
+ public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras,
+ PhysicalCaptureResultInfo physicalResults[]) throws RemoteException {
// TODO Auto-generated method stub
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 4c96d89..30561ba 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -35,6 +35,7 @@
import android.hardware.camera2.ICameraDeviceUser;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.utils.SubmitInfo;
import android.media.Image;
@@ -135,8 +136,8 @@
* android.hardware.camera2.impl.CameraMetadataNative,
* android.hardware.camera2.CaptureResultExtras)
*/
- public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras)
- throws RemoteException {
+ public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras,
+ PhysicalCaptureResultInfo physicalResults[]) throws RemoteException {
// TODO Auto-generated method stub
}
@@ -445,12 +446,14 @@
// Test both single request and streaming request.
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onResultReceived(
argThat(matcher),
- any(CaptureResultExtras.class));
+ any(CaptureResultExtras.class),
+ any(PhysicalCaptureResultInfo[].class));
verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
.onResultReceived(
argThat(matcher),
- any(CaptureResultExtras.class));
+ any(CaptureResultExtras.class),
+ any(PhysicalCaptureResultInfo[].class));
}
@SmallTest
diff --git a/native/android/asset_manager.cpp b/native/android/asset_manager.cpp
index 98e9a42..e70d5ea 100644
--- a/native/android/asset_manager.cpp
+++ b/native/android/asset_manager.cpp
@@ -18,9 +18,11 @@
#include <utils/Log.h>
#include <android/asset_manager_jni.h>
+#include <android_runtime/android_util_AssetManager.h>
#include <androidfw/Asset.h>
#include <androidfw/AssetDir.h>
#include <androidfw/AssetManager.h>
+#include <androidfw/AssetManager2.h>
#include <utils/threads.h>
#include "jni.h"
@@ -35,21 +37,20 @@
// -----
struct AAssetDir {
- AssetDir* mAssetDir;
+ std::unique_ptr<AssetDir> mAssetDir;
size_t mCurFileIndex;
String8 mCachedFileName;
- explicit AAssetDir(AssetDir* dir) : mAssetDir(dir), mCurFileIndex(0) { }
- ~AAssetDir() { delete mAssetDir; }
+ explicit AAssetDir(std::unique_ptr<AssetDir> dir) :
+ mAssetDir(std::move(dir)), mCurFileIndex(0) { }
};
// -----
struct AAsset {
- Asset* mAsset;
+ std::unique_ptr<Asset> mAsset;
- explicit AAsset(Asset* asset) : mAsset(asset) { }
- ~AAsset() { delete mAsset; }
+ explicit AAsset(std::unique_ptr<Asset> asset) : mAsset(std::move(asset)) { }
};
// -------------------- Public native C API --------------------
@@ -104,19 +105,18 @@
return NULL;
}
- AssetManager* mgr = static_cast<AssetManager*>(amgr);
- Asset* asset = mgr->open(filename, amMode);
- if (asset == NULL) {
- return NULL;
+ ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr));
+ std::unique_ptr<Asset> asset = locked_mgr->Open(filename, amMode);
+ if (asset == nullptr) {
+ return nullptr;
}
-
- return new AAsset(asset);
+ return new AAsset(std::move(asset));
}
AAssetDir* AAssetManager_openDir(AAssetManager* amgr, const char* dirName)
{
- AssetManager* mgr = static_cast<AssetManager*>(amgr);
- return new AAssetDir(mgr->openDir(dirName));
+ ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr));
+ return new AAssetDir(locked_mgr->OpenDir(dirName));
}
/**
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5dcc927..4156653 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1030,4 +1030,17 @@
<!-- Content description of zen mode time condition minus button (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_manual_zen_less_time">Less time.</string>
+ <!-- Do not disturb: Label for button in enable zen dialog that will turn on zen mode. [CHAR LIMIT=30] -->
+ <string name="zen_mode_enable_dialog_turn_on">Turn on</string>
+ <!-- Button label for generic cancel action [CHAR LIMIT=20] -->
+ <string name="cancel">Cancel</string>
+ <!-- Do not disturb: Title for the Do not Disturb dialog to turn on Do not disturb. [CHAR LIMIT=50]-->
+ <string name="zen_mode_settings_turn_on_dialog_title">Turn on Do Not Disturb</string>
+ <!-- Sound: Summary for the Do not Disturb option when there is no automatic rules turned on. [CHAR LIMIT=NONE]-->
+ <string name="zen_mode_settings_summary_off">Never</string>
+ <!--[CHAR LIMIT=40] Zen Interruption level: Priority. -->
+ <string name="zen_interruption_level_priority">Priority only</string>
+ <!-- [CHAR LIMIT=20] Accessibility string for current zen mode and selected exit condition. A template that simply concatenates existing mode string and the current condition description. -->
+ <string name="zen_mode_and_condition"><xliff:g id="zen_mode" example="Priority interruptions only">%1$s</xliff:g>. <xliff:g id="exit_condition" example="For one hour">%2$s</xliff:g></string>
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index d001e66..1f67dfb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -44,7 +44,11 @@
com.android.internal.R.drawable.ic_wifi_signal_4
};
- public static void updateLocationEnabled(Context context, boolean enabled, int userId) {
+ public static void updateLocationEnabled(Context context, boolean enabled, int userId,
+ int source) {
+ Settings.Secure.putIntForUser(
+ context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source,
+ userId);
Intent intent = new Intent(LocationManager.MODE_CHANGING_ACTION);
final int oldMode = Settings.Secure.getIntForUser(context.getContentResolver(),
@@ -62,7 +66,11 @@
wrapper.setLocationEnabledForUser(enabled, UserHandle.of(userId));
}
- public static boolean updateLocationMode(Context context, int oldMode, int newMode, int userId) {
+ public static boolean updateLocationMode(Context context, int oldMode, int newMode, int userId,
+ int source) {
+ Settings.Secure.putIntForUser(
+ context.getContentResolver(), Settings.Secure.LOCATION_CHANGER, source,
+ userId);
Intent intent = new Intent(LocationManager.MODE_CHANGING_ACTION);
intent.putExtra(CURRENT_MODE_KEY, oldMode);
intent.putExtra(NEW_MODE_KEY, newMode);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 9b69304..6b99024 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -219,8 +219,8 @@
return true;
}
BluetoothCodecConfig codecConfig = null;
- if (mServiceWrapper.getCodecStatus() != null) {
- codecConfig = mServiceWrapper.getCodecStatus().getCodecConfig();
+ if (mServiceWrapper.getCodecStatus(device) != null) {
+ codecConfig = mServiceWrapper.getCodecStatus(device).getCodecConfig();
}
if (codecConfig != null) {
return !codecConfig.isMandatoryCodec();
@@ -238,9 +238,9 @@
return;
}
if (enabled) {
- mService.enableOptionalCodecs();
+ mService.enableOptionalCodecs(device);
} else {
- mService.disableOptionalCodecs();
+ mService.disableOptionalCodecs(device);
}
}
@@ -253,8 +253,8 @@
// We want to get the highest priority codec, since that's the one that will be used with
// this device, and see if it is high-quality (ie non-mandatory).
BluetoothCodecConfig[] selectable = null;
- if (mServiceWrapper.getCodecStatus() != null) {
- selectable = mServiceWrapper.getCodecStatus().getCodecsSelectableCapabilities();
+ if (mServiceWrapper.getCodecStatus(device) != null) {
+ selectable = mServiceWrapper.getCodecStatus(device).getCodecsSelectableCapabilities();
// To get the highest priority, we sort in reverse.
Arrays.sort(selectable,
(a, b) -> {
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
new file mode 100644
index 0000000..7227304
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
@@ -0,0 +1,110 @@
+/*
+ * 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.core.instrumentation;
+
+import android.content.Context;
+import android.metrics.LogMaker;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+
+/**
+ * {@link LogWriter} that writes data to eventlog.
+ */
+public class EventLogWriter implements LogWriter {
+
+ private final MetricsLogger mMetricsLogger = new MetricsLogger();
+
+ public void visible(Context context, int source, int category) {
+ final LogMaker logMaker = new LogMaker(category)
+ .setType(MetricsProto.MetricsEvent.TYPE_OPEN)
+ .addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source);
+ MetricsLogger.action(logMaker);
+ }
+
+ public void hidden(Context context, int category) {
+ MetricsLogger.hidden(context, category);
+ }
+
+ public void action(int category, int value, Pair<Integer, Object>... taggedData) {
+ if (taggedData == null || taggedData.length == 0) {
+ mMetricsLogger.action(category, value);
+ } else {
+ final LogMaker logMaker = new LogMaker(category)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
+ .setSubtype(value);
+ for (Pair<Integer, Object> pair : taggedData) {
+ logMaker.addTaggedData(pair.first, pair.second);
+ }
+ mMetricsLogger.write(logMaker);
+ }
+ }
+
+ public void action(int category, boolean value, Pair<Integer, Object>... taggedData) {
+ action(category, value ? 1 : 0, taggedData);
+ }
+
+ public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
+ action(context, category, "", taggedData);
+ }
+
+ public void actionWithSource(Context context, int source, int category) {
+ final LogMaker logMaker = new LogMaker(category)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+ if (source != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
+ logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source);
+ }
+ MetricsLogger.action(logMaker);
+ }
+
+ /** @deprecated use {@link #action(int, int, Pair[])} */
+ @Deprecated
+ public void action(Context context, int category, int value) {
+ MetricsLogger.action(context, category, value);
+ }
+
+ /** @deprecated use {@link #action(int, boolean, Pair[])} */
+ @Deprecated
+ public void action(Context context, int category, boolean value) {
+ MetricsLogger.action(context, category, value);
+ }
+
+ public void action(Context context, int category, String pkg,
+ Pair<Integer, Object>... taggedData) {
+ if (taggedData == null || taggedData.length == 0) {
+ MetricsLogger.action(context, category, pkg);
+ } else {
+ final LogMaker logMaker = new LogMaker(category)
+ .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
+ .setPackageName(pkg);
+ for (Pair<Integer, Object> pair : taggedData) {
+ logMaker.addTaggedData(pair.first, pair.second);
+ }
+ MetricsLogger.action(logMaker);
+ }
+ }
+
+ public void count(Context context, String name, int value) {
+ MetricsLogger.count(context, name, value);
+ }
+
+ public void histogram(Context context, String name, int bucket) {
+ MetricsLogger.histogram(context, name, bucket);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/Instrumentable.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/Instrumentable.java
new file mode 100644
index 0000000..dbc61c2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/Instrumentable.java
@@ -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.
+ */
+
+package com.android.settingslib.core.instrumentation;
+
+public interface Instrumentable {
+
+ int METRICS_CATEGORY_UNKNOWN = 0;
+
+ /**
+ * Instrumented name for a view as defined in
+ * {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}.
+ */
+ int getMetricsCategory();
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
new file mode 100644
index 0000000..4b9f572
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -0,0 +1,84 @@
+/*
+ * 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.core.instrumentation;
+
+import android.content.Context;
+import android.util.Pair;
+
+/**
+ * Generic log writer interface.
+ */
+public interface LogWriter {
+
+ /**
+ * Logs a visibility event when view becomes visible.
+ */
+ void visible(Context context, int source, int category);
+
+ /**
+ * Logs a visibility event when view becomes hidden.
+ */
+ void hidden(Context context, int category);
+
+ /**
+ * Logs a user action.
+ */
+ void action(int category, int value, Pair<Integer, Object>... taggedData);
+
+ /**
+ * Logs a user action.
+ */
+ void action(int category, boolean value, Pair<Integer, Object>... taggedData);
+
+ /**
+ * Logs an user action.
+ */
+ void action(Context context, int category, Pair<Integer, Object>... taggedData);
+
+ /**
+ * Logs an user action.
+ */
+ void actionWithSource(Context context, int source, int category);
+
+ /**
+ * Logs an user action.
+ * @deprecated use {@link #action(int, int, Pair[])}
+ */
+ @Deprecated
+ void action(Context context, int category, int value);
+
+ /**
+ * Logs an user action.
+ * @deprecated use {@link #action(int, boolean, Pair[])}
+ */
+ @Deprecated
+ void action(Context context, int category, boolean value);
+
+ /**
+ * Logs an user action.
+ */
+ void action(Context context, int category, String pkg, Pair<Integer, Object>... taggedData);
+
+ /**
+ * Logs a count.
+ */
+ void count(Context context, String name, int value);
+
+ /**
+ * Logs a histogram event.
+ */
+ void histogram(Context context, String name, int bucket);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
new file mode 100644
index 0000000..1e5b378
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -0,0 +1,159 @@
+/*
+ * 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.core.instrumentation;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * FeatureProvider for metrics.
+ */
+public class MetricsFeatureProvider {
+ private List<LogWriter> mLoggerWriters;
+
+ public MetricsFeatureProvider() {
+ mLoggerWriters = new ArrayList<>();
+ installLogWriters();
+ }
+
+ protected void installLogWriters() {
+ mLoggerWriters.add(new EventLogWriter());
+ }
+
+ public void visible(Context context, int source, int category) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.visible(context, source, category);
+ }
+ }
+
+ public void hidden(Context context, int category) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.hidden(context, category);
+ }
+ }
+
+ public void actionWithSource(Context context, int source, int category) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.actionWithSource(context, source, category);
+ }
+ }
+
+ /**
+ * Logs a user action. Includes the elapsed time since the containing
+ * fragment has been visible.
+ */
+ public void action(VisibilityLoggerMixin visibilityLogger, int category, int value) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.action(category, value,
+ sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible()));
+ }
+ }
+
+ /**
+ * Logs a user action. Includes the elapsed time since the containing
+ * fragment has been visible.
+ */
+ public void action(VisibilityLoggerMixin visibilityLogger, int category, boolean value) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.action(category, value,
+ sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible()));
+ }
+ }
+
+ public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.action(context, category, taggedData);
+ }
+ }
+
+ /** @deprecated use {@link #action(VisibilityLoggerMixin, int, int)} */
+ @Deprecated
+ public void action(Context context, int category, int value) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.action(context, category, value);
+ }
+ }
+
+ /** @deprecated use {@link #action(VisibilityLoggerMixin, int, boolean)} */
+ @Deprecated
+ public void action(Context context, int category, boolean value) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.action(context, category, value);
+ }
+ }
+
+ public void action(Context context, int category, String pkg,
+ Pair<Integer, Object>... taggedData) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.action(context, category, pkg, taggedData);
+ }
+ }
+
+ public void count(Context context, String name, int value) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.count(context, name, value);
+ }
+ }
+
+ public void histogram(Context context, String name, int bucket) {
+ for (LogWriter writer : mLoggerWriters) {
+ writer.histogram(context, name, bucket);
+ }
+ }
+
+ public int getMetricsCategory(Object object) {
+ if (object == null || !(object instanceof Instrumentable)) {
+ return MetricsEvent.VIEW_UNKNOWN;
+ }
+ return ((Instrumentable) object).getMetricsCategory();
+ }
+
+ public void logDashboardStartIntent(Context context, Intent intent,
+ int sourceMetricsCategory) {
+ if (intent == null) {
+ return;
+ }
+ final ComponentName cn = intent.getComponent();
+ if (cn == null) {
+ final String action = intent.getAction();
+ if (TextUtils.isEmpty(action)) {
+ // Not loggable
+ return;
+ }
+ action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, action,
+ Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
+ return;
+ } else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) {
+ // Going to a Setting internal page, skip click logging in favor of page's own
+ // visibility logging.
+ return;
+ }
+ action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, cn.flattenToString(),
+ Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
+ }
+
+ private Pair<Integer, Object> sinceVisibleTaggedData(long timestamp) {
+ return Pair.create(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, timestamp);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
new file mode 100644
index 0000000..facce4e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
@@ -0,0 +1,259 @@
+/*
+ * 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.core.instrumentation;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+
+public class SharedPreferencesLogger implements SharedPreferences {
+
+ private static final String LOG_TAG = "SharedPreferencesLogger";
+
+ private final String mTag;
+ private final Context mContext;
+ private final MetricsFeatureProvider mMetricsFeature;
+ private final Set<String> mPreferenceKeySet;
+
+ public SharedPreferencesLogger(Context context, String tag,
+ MetricsFeatureProvider metricsFeature) {
+ mContext = context;
+ mTag = tag;
+ mMetricsFeature = metricsFeature;
+ mPreferenceKeySet = new ConcurrentSkipListSet<>();
+ }
+
+ @Override
+ public Map<String, ?> getAll() {
+ return null;
+ }
+
+ @Override
+ public String getString(String key, @Nullable String defValue) {
+ return defValue;
+ }
+
+ @Override
+ public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
+ return defValues;
+ }
+
+ @Override
+ public int getInt(String key, int defValue) {
+ return defValue;
+ }
+
+ @Override
+ public long getLong(String key, long defValue) {
+ return defValue;
+ }
+
+ @Override
+ public float getFloat(String key, float defValue) {
+ return defValue;
+ }
+
+ @Override
+ public boolean getBoolean(String key, boolean defValue) {
+ return defValue;
+ }
+
+ @Override
+ public boolean contains(String key) {
+ return false;
+ }
+
+ @Override
+ public Editor edit() {
+ return new EditorLogger();
+ }
+
+ @Override
+ public void registerOnSharedPreferenceChangeListener(
+ OnSharedPreferenceChangeListener listener) {
+ }
+
+ @Override
+ public void unregisterOnSharedPreferenceChangeListener(
+ OnSharedPreferenceChangeListener listener) {
+ }
+
+ private void logValue(String key, Object value) {
+ logValue(key, value, false /* forceLog */);
+ }
+
+ private void logValue(String key, Object value, boolean forceLog) {
+ final String prefKey = buildPrefKey(mTag, key);
+ if (!forceLog && !mPreferenceKeySet.contains(prefKey)) {
+ // Pref key doesn't exist in set, this is initial display so we skip metrics but
+ // keeps track of this key.
+ mPreferenceKeySet.add(prefKey);
+ return;
+ }
+ // TODO: Remove count logging to save some resource.
+ mMetricsFeature.count(mContext, buildCountName(prefKey, value), 1);
+
+ final Pair<Integer, Object> valueData;
+ if (value instanceof Long) {
+ final Long longVal = (Long) value;
+ final int intVal;
+ if (longVal > Integer.MAX_VALUE) {
+ intVal = Integer.MAX_VALUE;
+ } else if (longVal < Integer.MIN_VALUE) {
+ intVal = Integer.MIN_VALUE;
+ } else {
+ intVal = longVal.intValue();
+ }
+ valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+ intVal);
+ } else if (value instanceof Integer) {
+ valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+ value);
+ } else if (value instanceof Boolean) {
+ valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+ (Boolean) value ? 1 : 0);
+ } else if (value instanceof Float) {
+ valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE,
+ value);
+ } else if (value instanceof String) {
+ Log.d(LOG_TAG, "Tried to log string preference " + prefKey + " = " + value);
+ valueData = null;
+ } else {
+ Log.w(LOG_TAG, "Tried to log unloggable object" + value);
+ valueData = null;
+ }
+ if (valueData != null) {
+ // Pref key exists in set, log it's change in metrics.
+ mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE,
+ Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey),
+ valueData);
+ }
+ }
+
+ @VisibleForTesting
+ void logPackageName(String key, String value) {
+ final String prefKey = mTag + "/" + key;
+ mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE, value,
+ Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey));
+ }
+
+ private void safeLogValue(String key, String value) {
+ new AsyncPackageCheck().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key, value);
+ }
+
+ public static String buildCountName(String prefKey, Object value) {
+ return prefKey + "|" + value;
+ }
+
+ public static String buildPrefKey(String tag, String key) {
+ return tag + "/" + key;
+ }
+
+ private class AsyncPackageCheck extends AsyncTask<String, Void, Void> {
+ @Override
+ protected Void doInBackground(String... params) {
+ String key = params[0];
+ String value = params[1];
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ // Check if this might be a component.
+ ComponentName name = ComponentName.unflattenFromString(value);
+ if (value != null) {
+ value = name.getPackageName();
+ }
+ } catch (Exception e) {
+ }
+ try {
+ pm.getPackageInfo(value, PackageManager.MATCH_ANY_USER);
+ logPackageName(key, value);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Clearly not a package, and it's unlikely this preference is in prefSet, so
+ // lets force log it.
+ logValue(key, value, true /* forceLog */);
+ }
+ return null;
+ }
+ }
+
+ public class EditorLogger implements Editor {
+ @Override
+ public Editor putString(String key, @Nullable String value) {
+ safeLogValue(key, value);
+ return this;
+ }
+
+ @Override
+ public Editor putStringSet(String key, @Nullable Set<String> values) {
+ safeLogValue(key, TextUtils.join(",", values));
+ return this;
+ }
+
+ @Override
+ public Editor putInt(String key, int value) {
+ logValue(key, value);
+ return this;
+ }
+
+ @Override
+ public Editor putLong(String key, long value) {
+ logValue(key, value);
+ return this;
+ }
+
+ @Override
+ public Editor putFloat(String key, float value) {
+ logValue(key, value);
+ return this;
+ }
+
+ @Override
+ public Editor putBoolean(String key, boolean value) {
+ logValue(key, value);
+ return this;
+ }
+
+ @Override
+ public Editor remove(String key) {
+ return this;
+ }
+
+ @Override
+ public Editor clear() {
+ return this;
+ }
+
+ @Override
+ public boolean commit() {
+ return true;
+ }
+
+ @Override
+ public void apply() {
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
new file mode 100644
index 0000000..7983896
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -0,0 +1,96 @@
+/*
+ * 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.core.instrumentation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+
+import android.os.SystemClock;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+
+import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;
+
+/**
+ * Logs visibility change of a fragment.
+ */
+public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPause {
+
+ private static final String TAG = "VisibilityLoggerMixin";
+
+ private final int mMetricsCategory;
+
+ private MetricsFeatureProvider mMetricsFeature;
+ private int mSourceMetricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN;
+ private long mVisibleTimestamp;
+
+ /**
+ * The metrics category constant for logging source when a setting fragment is opened.
+ */
+ public static final String EXTRA_SOURCE_METRICS_CATEGORY = ":settings:source_metrics";
+
+ private VisibilityLoggerMixin() {
+ mMetricsCategory = METRICS_CATEGORY_UNKNOWN;
+ }
+
+ public VisibilityLoggerMixin(int metricsCategory, MetricsFeatureProvider metricsFeature) {
+ mMetricsCategory = metricsCategory;
+ mMetricsFeature = metricsFeature;
+ }
+
+ @Override
+ public void onResume() {
+ mVisibleTimestamp = SystemClock.elapsedRealtime();
+ if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
+ mMetricsFeature.visible(null /* context */, mSourceMetricsCategory, mMetricsCategory);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ mVisibleTimestamp = 0;
+ if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
+ mMetricsFeature.hidden(null /* context */, mMetricsCategory);
+ }
+ }
+
+ /**
+ * Sets source metrics category for this logger. Source is the caller that opened this UI.
+ */
+ public void setSourceMetricsCategory(Activity activity) {
+ if (mSourceMetricsCategory != MetricsProto.MetricsEvent.VIEW_UNKNOWN || activity == null) {
+ return;
+ }
+ final Intent intent = activity.getIntent();
+ if (intent == null) {
+ return;
+ }
+ mSourceMetricsCategory = intent.getIntExtra(EXTRA_SOURCE_METRICS_CATEGORY,
+ MetricsProto.MetricsEvent.VIEW_UNKNOWN);
+ }
+
+ /** Returns elapsed time since onResume() */
+ public long elapsedTimeSinceVisible() {
+ if (mVisibleTimestamp == 0) {
+ return 0;
+ }
+ return SystemClock.elapsedRealtime() - mVisibleTimestamp;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
new file mode 100644
index 0000000..a20f687
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -0,0 +1,439 @@
+package com.android.settingslib.notification;
+
+/*
+ * Copyright (C) 2018 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.
+ */
+
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.policy.PhoneWindow;
+import com.android.settingslib.R;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Objects;
+
+public class EnableZenModeDialog {
+
+ private static final String TAG = "QSEnableZenModeDialog";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
+ private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
+ private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
+ private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
+
+ private static final int FOREVER_CONDITION_INDEX = 0;
+ private static final int COUNTDOWN_CONDITION_INDEX = 1;
+ private static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2;
+
+ private static final int SECONDS_MS = 1000;
+ private static final int MINUTES_MS = 60 * SECONDS_MS;
+
+ private Uri mForeverId;
+ private int mBucketIndex = -1;
+
+ private AlarmManager mAlarmManager;
+ private int mUserId;
+ private boolean mAttached;
+
+ private Context mContext;
+ private RadioGroup mZenRadioGroup;
+ private LinearLayout mZenRadioGroupContent;
+ private int MAX_MANUAL_DND_OPTIONS = 3;
+
+ public EnableZenModeDialog(Context context) {
+ mContext = context;
+ }
+
+ public Dialog createDialog() {
+ NotificationManager noMan = (NotificationManager) mContext.
+ getSystemService(Context.NOTIFICATION_SERVICE);
+ mForeverId = Condition.newId(mContext).appendPath("forever").build();
+ mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ mUserId = mContext.getUserId();
+ mAttached = false;
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
+ .setTitle(R.string.zen_mode_settings_turn_on_dialog_title)
+ .setNegativeButton(R.string.cancel, null)
+ .setPositiveButton(R.string.zen_mode_enable_dialog_turn_on,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ int checkedId = mZenRadioGroup.getCheckedRadioButtonId();
+ ConditionTag tag = getConditionTagAt(checkedId);
+
+ if (isForever(tag.condition)) {
+ MetricsLogger.action(mContext,
+ MetricsProto.MetricsEvent.
+ NOTIFICATION_ZEN_MODE_TOGGLE_ON_FOREVER);
+ } else if (isAlarm(tag.condition)) {
+ MetricsLogger.action(mContext,
+ MetricsProto.MetricsEvent.
+ NOTIFICATION_ZEN_MODE_TOGGLE_ON_ALARM);
+ } else if (isCountdown(tag.condition)) {
+ MetricsLogger.action(mContext,
+ MetricsProto.MetricsEvent.
+ NOTIFICATION_ZEN_MODE_TOGGLE_ON_COUNTDOWN);
+ } else {
+ Slog.d(TAG, "Invalid manual condition: " + tag.condition);
+ }
+ // always triggers priority-only dnd with chosen condition
+ noMan.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ getRealConditionId(tag.condition), TAG);
+ }
+ });
+
+ View contentView = getContentView();
+ bindConditions(forever());
+ builder.setView(contentView);
+ return builder.create();
+ }
+
+ private void hideAllConditions() {
+ final int N = mZenRadioGroupContent.getChildCount();
+ for (int i = 0; i < N; i++) {
+ mZenRadioGroupContent.getChildAt(i).setVisibility(View.GONE);
+ }
+ }
+
+ protected View getContentView() {
+ final LayoutInflater inflater = new PhoneWindow(mContext).getLayoutInflater();
+ View contentView = inflater.inflate(R.layout.zen_mode_turn_on_dialog_container, null);
+ ScrollView container = (ScrollView) contentView.findViewById(R.id.container);
+
+ mZenRadioGroup = container.findViewById(R.id.zen_radio_buttons);
+ mZenRadioGroupContent = container.findViewById(R.id.zen_radio_buttons_content);
+
+ for (int i = 0; i < MAX_MANUAL_DND_OPTIONS; i++) {
+ final View radioButton = inflater.inflate(R.layout.zen_mode_radio_button,
+ mZenRadioGroup, false);
+ mZenRadioGroup.addView(radioButton);
+ radioButton.setId(i);
+
+ final View radioButtonContent = inflater.inflate(R.layout.zen_mode_condition,
+ mZenRadioGroupContent, false);
+ radioButtonContent.setId(i + MAX_MANUAL_DND_OPTIONS);
+ mZenRadioGroupContent.addView(radioButtonContent);
+ }
+ hideAllConditions();
+ return contentView;
+ }
+
+ private void bind(final Condition condition, final View row, final int rowId) {
+ if (condition == null) throw new IllegalArgumentException("condition must not be null");
+ final boolean enabled = condition.state == Condition.STATE_TRUE;
+ final ConditionTag tag = row.getTag() != null ? (ConditionTag) row.getTag() :
+ new ConditionTag();
+ row.setTag(tag);
+ final boolean first = tag.rb == null;
+ if (tag.rb == null) {
+ tag.rb = (RadioButton) mZenRadioGroup.getChildAt(rowId);
+ }
+ tag.condition = condition;
+ final Uri conditionId = getConditionId(tag.condition);
+ if (DEBUG) Log.d(TAG, "bind i=" + mZenRadioGroupContent.indexOfChild(row) + " first="
+ + first + " condition=" + conditionId);
+ tag.rb.setEnabled(enabled);
+ tag.rb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ tag.rb.setChecked(true);
+ if (DEBUG) Log.d(TAG, "onCheckedChanged " + conditionId);
+ MetricsLogger.action(mContext,
+ MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT);
+ announceConditionSelection(tag);
+ }
+ }
+ });
+
+ updateUi(tag, row, condition, enabled, rowId, conditionId);
+ row.setVisibility(View.VISIBLE);
+ }
+
+ private ConditionTag getConditionTagAt(int index) {
+ return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag();
+ }
+
+ private void bindConditions(Condition c) {
+ // forever
+ bind(forever(), mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX),
+ FOREVER_CONDITION_INDEX);
+ if (c == null) {
+ bindGenericCountdown();
+ bindNextAlarm(getTimeUntilNextAlarmCondition());
+ } else if (isForever(c)) {
+ getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
+ bindGenericCountdown();
+ bindNextAlarm(getTimeUntilNextAlarmCondition());
+ } else {
+ if (isAlarm(c)) {
+ bindGenericCountdown();
+ bindNextAlarm(c);
+ getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.setChecked(true);
+ } else if (isCountdown(c)) {
+ bindNextAlarm(getTimeUntilNextAlarmCondition());
+ bind(c, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
+ COUNTDOWN_CONDITION_INDEX);
+ getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
+ } else {
+ Slog.d(TAG, "Invalid manual condition: " + c);
+ }
+ }
+ }
+
+ public static Uri getConditionId(Condition condition) {
+ return condition != null ? condition.id : null;
+ }
+
+ public Condition forever() {
+ Uri foreverId = Condition.newId(mContext).appendPath("forever").build();
+ return new Condition(foreverId, foreverSummary(mContext), "", "", 0 /*icon*/,
+ Condition.STATE_TRUE, 0 /*flags*/);
+ }
+
+ public long getNextAlarm() {
+ final AlarmManager.AlarmClockInfo info = mAlarmManager.getNextAlarmClock(mUserId);
+ return info != null ? info.getTriggerTime() : 0;
+ }
+
+ private boolean isAlarm(Condition c) {
+ return c != null && ZenModeConfig.isValidCountdownToAlarmConditionId(c.id);
+ }
+
+ private boolean isCountdown(Condition c) {
+ return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
+ }
+
+ private boolean isForever(Condition c) {
+ return c != null && mForeverId.equals(c.id);
+ }
+
+ private Uri getRealConditionId(Condition condition) {
+ return isForever(condition) ? null : getConditionId(condition);
+ }
+
+ private String foreverSummary(Context context) {
+ return context.getString(com.android.internal.R.string.zen_mode_forever);
+ }
+
+ private static void setToMidnight(Calendar calendar) {
+ calendar.set(Calendar.HOUR_OF_DAY, 0);
+ calendar.set(Calendar.MINUTE, 0);
+ calendar.set(Calendar.SECOND, 0);
+ calendar.set(Calendar.MILLISECOND, 0);
+ }
+
+ // Returns a time condition if the next alarm is within the next week.
+ private Condition getTimeUntilNextAlarmCondition() {
+ GregorianCalendar weekRange = new GregorianCalendar();
+ setToMidnight(weekRange);
+ weekRange.add(Calendar.DATE, 6);
+ final long nextAlarmMs = getNextAlarm();
+ if (nextAlarmMs > 0) {
+ GregorianCalendar nextAlarm = new GregorianCalendar();
+ nextAlarm.setTimeInMillis(nextAlarmMs);
+ setToMidnight(nextAlarm);
+
+ if (weekRange.compareTo(nextAlarm) >= 0) {
+ return ZenModeConfig.toNextAlarmCondition(mContext, nextAlarmMs,
+ ActivityManager.getCurrentUser());
+ }
+ }
+ return null;
+ }
+
+ private void bindGenericCountdown() {
+ mBucketIndex = DEFAULT_BUCKET_INDEX;
+ Condition countdown = ZenModeConfig.toTimeCondition(mContext,
+ MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
+ if (!mAttached || getConditionTagAt(COUNTDOWN_CONDITION_INDEX).condition == null) {
+ bind(countdown, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
+ COUNTDOWN_CONDITION_INDEX);
+ }
+ }
+
+ private void updateUi(ConditionTag tag, View row, Condition condition,
+ boolean enabled, int rowId, Uri conditionId) {
+ if (tag.lines == null) {
+ tag.lines = row.findViewById(android.R.id.content);
+ }
+ if (tag.line1 == null) {
+ tag.line1 = (TextView) row.findViewById(android.R.id.text1);
+ }
+
+ if (tag.line2 == null) {
+ tag.line2 = (TextView) row.findViewById(android.R.id.text2);
+ }
+
+ final String line1 = !TextUtils.isEmpty(condition.line1) ? condition.line1
+ : condition.summary;
+ final String line2 = condition.line2;
+ tag.line1.setText(line1);
+ if (TextUtils.isEmpty(line2)) {
+ tag.line2.setVisibility(View.GONE);
+ } else {
+ tag.line2.setVisibility(View.VISIBLE);
+ tag.line2.setText(line2);
+ }
+ tag.lines.setEnabled(enabled);
+ tag.lines.setAlpha(enabled ? 1 : .4f);
+
+ tag.lines.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ tag.rb.setChecked(true);
+ }
+ });
+
+ // minus button
+ final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
+ button1.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onClickTimeButton(row, tag, false /*down*/, rowId);
+ }
+ });
+
+ // plus button
+ final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
+ button2.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onClickTimeButton(row, tag, true /*up*/, rowId);
+ }
+ });
+
+ final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
+ if (rowId == COUNTDOWN_CONDITION_INDEX && time > 0) {
+ button1.setVisibility(View.VISIBLE);
+ button2.setVisibility(View.VISIBLE);
+ if (mBucketIndex > -1) {
+ button1.setEnabled(mBucketIndex > 0);
+ button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
+ } else {
+ final long span = time - System.currentTimeMillis();
+ button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
+ final Condition maxCondition = ZenModeConfig.toTimeCondition(mContext,
+ MAX_BUCKET_MINUTES, ActivityManager.getCurrentUser());
+ button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
+ }
+
+ button1.setAlpha(button1.isEnabled() ? 1f : .5f);
+ button2.setAlpha(button2.isEnabled() ? 1f : .5f);
+ } else {
+ button1.setVisibility(View.GONE);
+ button2.setVisibility(View.GONE);
+ }
+ }
+
+ private void bindNextAlarm(Condition c) {
+ View alarmContent = mZenRadioGroupContent.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX);
+ ConditionTag tag = (ConditionTag) alarmContent.getTag();
+
+ if (c != null && (!mAttached || tag == null || tag.condition == null)) {
+ bind(c, alarmContent, COUNTDOWN_ALARM_CONDITION_INDEX);
+ }
+
+ // hide the alarm radio button if there isn't a "next alarm condition"
+ tag = (ConditionTag) alarmContent.getTag();
+ boolean showAlarm = tag != null && tag.condition != null;
+ mZenRadioGroup.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility(
+ showAlarm ? View.VISIBLE : View.GONE);
+ alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.GONE);
+ }
+
+ private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) {
+ MetricsLogger.action(mContext, MetricsProto.MetricsEvent.QS_DND_TIME, up);
+ Condition newCondition = null;
+ final int N = MINUTE_BUCKETS.length;
+ if (mBucketIndex == -1) {
+ // not on a known index, search for the next or prev bucket by time
+ final Uri conditionId = getConditionId(tag.condition);
+ final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
+ final long now = System.currentTimeMillis();
+ for (int i = 0; i < N; i++) {
+ int j = up ? i : N - 1 - i;
+ final int bucketMinutes = MINUTE_BUCKETS[j];
+ final long bucketTime = now + bucketMinutes * MINUTES_MS;
+ if (up && bucketTime > time || !up && bucketTime < time) {
+ mBucketIndex = j;
+ newCondition = ZenModeConfig.toTimeCondition(mContext,
+ bucketTime, bucketMinutes, ActivityManager.getCurrentUser(),
+ false /*shortVersion*/);
+ break;
+ }
+ }
+ if (newCondition == null) {
+ mBucketIndex = DEFAULT_BUCKET_INDEX;
+ newCondition = ZenModeConfig.toTimeCondition(mContext,
+ MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
+ }
+ } else {
+ // on a known index, simply increment or decrement
+ mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1)));
+ newCondition = ZenModeConfig.toTimeCondition(mContext,
+ MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
+ }
+ bind(newCondition, row, rowId);
+ tag.rb.setChecked(true);
+ announceConditionSelection(tag);
+ }
+
+ private void announceConditionSelection(ConditionTag tag) {
+ // condition will always be priority-only
+ String modeText = mContext.getString(R.string.zen_interruption_level_priority);
+ if (tag.line1 != null) {
+ mZenRadioGroupContent.announceForAccessibility(mContext.getString(
+ R.string.zen_mode_and_condition, modeText, tag.line1.getText()));
+ }
+ }
+
+ // used as the view tag on condition rows
+ private static class ConditionTag {
+ public RadioButton rb;
+ public View lines;
+ public TextView line1;
+ public TextView line2;
+ public Condition condition;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
index 4c52a9f..17e3401 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
@@ -43,8 +43,8 @@
/**
* Wraps {@code BluetoothA2dp.getCodecStatus}
*/
- public BluetoothCodecStatus getCodecStatus() {
- return mService.getCodecStatus();
+ public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
+ return mService.getCodecStatus(device);
}
/**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 327c1c8..5459fb7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -84,23 +84,31 @@
mContext,
Secure.LOCATION_MODE_OFF,
Secure.LOCATION_MODE_HIGH_ACCURACY,
- currentUserId);
+ currentUserId,
+ Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
verify(mContext).sendBroadcastAsUser(
argThat(actionMatches(LocationManager.MODE_CHANGING_ACTION)),
ArgumentMatchers.eq(UserHandle.of(currentUserId)),
ArgumentMatchers.eq(WRITE_SECURE_SETTINGS));
+ assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.LOCATION_CHANGER, Settings.Secure.LOCATION_CHANGER_UNKNOWN))
+ .isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
}
@Test
public void testUpdateLocationEnabled_sendBroadcast() {
int currentUserId = ActivityManager.getCurrentUser();
- Utils.updateLocationEnabled(mContext, true, currentUserId);
+ Utils.updateLocationEnabled(mContext, true, currentUserId,
+ Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
verify(mContext).sendBroadcastAsUser(
argThat(actionMatches(LocationManager.MODE_CHANGING_ACTION)),
ArgumentMatchers.eq(UserHandle.of(currentUserId)),
ArgumentMatchers.eq(WRITE_SECURE_SETTINGS));
+ assertThat(Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.LOCATION_CHANGER, Settings.Secure.LOCATION_CHANGER_UNKNOWN))
+ .isEqualTo(Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index ece0d51..590bc90 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -123,7 +123,7 @@
when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
BluetoothProfile.STATE_CONNECTED);
BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
- when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+ when(mBluetoothA2dpWrapper.getCodecStatus(mDevice)).thenReturn(status);
BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
when(status.getCodecConfig()).thenReturn(config);
when(config.isMandatoryCodec()).thenReturn(false);
@@ -186,7 +186,7 @@
BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
BluetoothCodecConfig[] configs = {config};
- when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+ when(mBluetoothA2dpWrapper.getCodecStatus(mDevice)).thenReturn(status);
when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
when(config.isMandatoryCodec()).thenReturn(true);
@@ -201,7 +201,7 @@
BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
BluetoothCodecConfig[] configs = {config};
- when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+ when(mBluetoothA2dpWrapper.getCodecStatus(mDevice)).thenReturn(status);
when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
when(config.isMandatoryCodec()).thenReturn(false);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
new file mode 100644
index 0000000..8bea51d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.core.instrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Pair;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class MetricsFeatureProviderTest {
+ private static int CATEGORY = 10;
+ private static boolean SUBTYPE_BOOLEAN = true;
+ private static int SUBTYPE_INTEGER = 1;
+ private static long ELAPSED_TIME = 1000;
+
+ @Mock private LogWriter mockLogWriter;
+ @Mock private VisibilityLoggerMixin mockVisibilityLogger;
+
+ private Context mContext;
+ private MetricsFeatureProvider mProvider;
+
+ @Captor
+ private ArgumentCaptor<Pair> mPairCaptor;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mProvider = new MetricsFeatureProvider();
+ List<LogWriter> writers = new ArrayList<>();
+ writers.add(mockLogWriter);
+ ReflectionHelpers.setField(mProvider, "mLoggerWriters", writers);
+
+ when(mockVisibilityLogger.elapsedTimeSinceVisible()).thenReturn(ELAPSED_TIME);
+ }
+
+ @Test
+ public void logDashboardStartIntent_intentEmpty_shouldNotLog() {
+ mProvider.logDashboardStartIntent(mContext, null /* intent */,
+ MetricsEvent.SETTINGS_GESTURES);
+
+ verifyNoMoreInteractions(mockLogWriter);
+ }
+
+ @Test
+ public void logDashboardStartIntent_intentHasNoComponent_shouldLog() {
+ final Intent intent = new Intent(Intent.ACTION_ASSIST);
+
+ mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
+
+ verify(mockLogWriter).action(
+ eq(mContext),
+ eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK),
+ anyString(),
+ eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES)));
+ }
+
+ @Test
+ public void logDashboardStartIntent_intentIsExternal_shouldLog() {
+ final Intent intent = new Intent().setComponent(new ComponentName("pkg", "cls"));
+
+ mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
+
+ verify(mockLogWriter).action(
+ eq(mContext),
+ eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK),
+ anyString(),
+ eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES)));
+ }
+
+ @Test
+ public void action_BooleanLogsElapsedTime() {
+ mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_BOOLEAN);
+ verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_BOOLEAN), mPairCaptor.capture());
+
+ Pair value = mPairCaptor.getValue();
+ assertThat(value.first instanceof Integer).isTrue();
+ assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS);
+ assertThat(value.second).isEqualTo(ELAPSED_TIME);
+ }
+
+ @Test
+ public void action_IntegerLogsElapsedTime() {
+ mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_INTEGER);
+ verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_INTEGER), mPairCaptor.capture());
+
+ Pair value = mPairCaptor.getValue();
+ assertThat(value.first instanceof Integer).isTrue();
+ assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS);
+ assertThat(value.second).isEqualTo(ELAPSED_TIME);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
new file mode 100644
index 0000000..d558a64
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.core.instrumentation;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Pair;
+
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+
+import com.google.common.truth.Platform;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SharedPreferenceLoggerTest {
+
+ private static final String TEST_TAG = "tag";
+ private static final String TEST_KEY = "key";
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Context mContext;
+
+ private ArgumentMatcher<Pair<Integer, Object>> mNamePairMatcher;
+ @Mock
+ private MetricsFeatureProvider mMetricsFeature;
+ private SharedPreferencesLogger mSharedPrefLogger;
+
+ @Before
+ public void init() {
+ MockitoAnnotations.initMocks(this);
+ mSharedPrefLogger = new SharedPreferencesLogger(mContext, TEST_TAG, mMetricsFeature);
+ mNamePairMatcher = pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, String.class);
+ }
+
+ @Test
+ public void putInt_shouldNotLogInitialPut() {
+ final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+ editor.putInt(TEST_KEY, 1);
+ editor.putInt(TEST_KEY, 1);
+ editor.putInt(TEST_KEY, 1);
+ editor.putInt(TEST_KEY, 2);
+ editor.putInt(TEST_KEY, 2);
+ editor.putInt(TEST_KEY, 2);
+ editor.putInt(TEST_KEY, 2);
+
+ verify(mMetricsFeature, times(6)).action(any(Context.class), anyInt(),
+ argThat(mNamePairMatcher),
+ argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class)));
+ }
+
+ @Test
+ public void putBoolean_shouldNotLogInitialPut() {
+ final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+ editor.putBoolean(TEST_KEY, true);
+ editor.putBoolean(TEST_KEY, true);
+ editor.putBoolean(TEST_KEY, false);
+ editor.putBoolean(TEST_KEY, false);
+ editor.putBoolean(TEST_KEY, false);
+
+
+ verify(mMetricsFeature).action(any(Context.class), anyInt(),
+ argThat(mNamePairMatcher),
+ argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, true)));
+ verify(mMetricsFeature, times(3)).action(any(Context.class), anyInt(),
+ argThat(mNamePairMatcher),
+ argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, false)));
+ }
+
+ @Test
+ public void putLong_shouldNotLogInitialPut() {
+ final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+ editor.putLong(TEST_KEY, 1);
+ editor.putLong(TEST_KEY, 1);
+ editor.putLong(TEST_KEY, 1);
+ editor.putLong(TEST_KEY, 1);
+ editor.putLong(TEST_KEY, 2);
+
+ verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(),
+ argThat(mNamePairMatcher),
+ argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class)));
+ }
+
+ @Test
+ public void putLong_biggerThanIntMax_shouldLogIntMax() {
+ final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+ final long veryBigNumber = 500L + Integer.MAX_VALUE;
+ editor.putLong(TEST_KEY, 1);
+ editor.putLong(TEST_KEY, veryBigNumber);
+
+ verify(mMetricsFeature).action(any(Context.class), anyInt(),
+ argThat(mNamePairMatcher),
+ argThat(pairMatches(
+ FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MAX_VALUE)));
+ }
+
+ @Test
+ public void putLong_smallerThanIntMin_shouldLogIntMin() {
+ final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+ final long veryNegativeNumber = -500L + Integer.MIN_VALUE;
+ editor.putLong(TEST_KEY, 1);
+ editor.putLong(TEST_KEY, veryNegativeNumber);
+
+ verify(mMetricsFeature).action(any(Context.class), anyInt(),
+ argThat(mNamePairMatcher),
+ argThat(pairMatches(
+ FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MIN_VALUE)));
+ }
+
+ @Test
+ public void putFloat_shouldNotLogInitialPut() {
+ final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+ editor.putFloat(TEST_KEY, 1);
+ editor.putFloat(TEST_KEY, 1);
+ editor.putFloat(TEST_KEY, 1);
+ editor.putFloat(TEST_KEY, 1);
+ editor.putFloat(TEST_KEY, 2);
+
+ verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(),
+ argThat(mNamePairMatcher),
+ argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE, Float.class)));
+ }
+
+ @Test
+ public void logPackage_shouldUseLogPackageApi() {
+ mSharedPrefLogger.logPackageName("key", "com.android.settings");
+ verify(mMetricsFeature).action(any(Context.class),
+ eq(ACTION_SETTINGS_PREFERENCE_CHANGE),
+ eq("com.android.settings"),
+ any(Pair.class));
+ }
+
+ private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, Class clazz) {
+ return pair -> pair.first == tag && Platform.isInstanceOfType(pair.second, clazz);
+ }
+
+ private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, boolean bool) {
+ return pair -> pair.first == tag
+ && Platform.isInstanceOfType(pair.second, Integer.class)
+ && pair.second.equals((bool ? 1 : 0));
+ }
+
+ private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, int val) {
+ return pair -> pair.first == tag
+ && Platform.isInstanceOfType(pair.second, Integer.class)
+ && pair.second.equals(val);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
new file mode 100644
index 0000000..a264886
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.core.instrumentation;
+
+import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class VisibilityLoggerMixinTest {
+
+ @Mock
+ private MetricsFeatureProvider mMetricsFeature;
+
+ private VisibilityLoggerMixin mMixin;
+
+ @Before
+ public void init() {
+ MockitoAnnotations.initMocks(this);
+ mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC, mMetricsFeature);
+ }
+
+ @Test
+ public void shouldLogVisibleOnResume() {
+ mMixin.onResume();
+
+ verify(mMetricsFeature, times(1))
+ .visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.VIEW_UNKNOWN),
+ eq(TestInstrumentable.TEST_METRIC));
+ }
+
+ @Test
+ public void shouldLogVisibleWithSource() {
+ final Intent sourceIntent = new Intent()
+ .putExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY,
+ MetricsProto.MetricsEvent.SETTINGS_GESTURES);
+ final Activity activity = mock(Activity.class);
+ when(activity.getIntent()).thenReturn(sourceIntent);
+ mMixin.setSourceMetricsCategory(activity);
+ mMixin.onResume();
+
+ verify(mMetricsFeature, times(1))
+ .visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.SETTINGS_GESTURES),
+ eq(TestInstrumentable.TEST_METRIC));
+ }
+
+ @Test
+ public void shouldLogHideOnPause() {
+ mMixin.onPause();
+
+ verify(mMetricsFeature, times(1))
+ .hidden(nullable(Context.class), eq(TestInstrumentable.TEST_METRIC));
+ }
+
+ @Test
+ public void shouldNotLogIfMetricsFeatureIsNull() {
+ mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC, null);
+ mMixin.onResume();
+ mMixin.onPause();
+
+ verify(mMetricsFeature, never())
+ .hidden(nullable(Context.class), anyInt());
+ }
+
+ @Test
+ public void shouldNotLogIfMetricsCategoryIsUnknown() {
+ mMixin = new VisibilityLoggerMixin(METRICS_CATEGORY_UNKNOWN, mMetricsFeature);
+
+ mMixin.onResume();
+ mMixin.onPause();
+
+ verify(mMetricsFeature, never())
+ .hidden(nullable(Context.class), anyInt());
+ }
+
+ private final class TestInstrumentable implements Instrumentable {
+
+ public static final int TEST_METRIC = 12345;
+
+ @Override
+ public int getMetricsCategory() {
+ return TEST_METRIC;
+ }
+ }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index cfd33a1..91957e1 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -30,6 +30,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
import android.icu.util.ULocale;
import android.location.LocationManager;
import android.media.AudioManager;
@@ -306,15 +307,7 @@
}
private void setBrightness(int brightness) {
- try {
- IPowerManager power = IPowerManager.Stub.asInterface(
- ServiceManager.getService("power"));
- if (power != null) {
- power.setTemporaryScreenBrightnessSettingOverride(brightness);
- }
- } catch (RemoteException doe) {
-
- }
+ mContext.getSystemService(DisplayManager.class).setTemporaryBrightness(brightness);
}
/* package */ byte[] getLocaleData() {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index d556db4..6970c86 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.os.UserHandle;
import android.provider.Settings;
+import android.provider.Settings.Global;
import android.providers.settings.GlobalSettingsProto;
import android.providers.settings.SecureSettingsProto;
import android.providers.settings.SettingProto;
@@ -1137,6 +1138,12 @@
dumpSetting(s, p,
Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
GlobalSettingsProto.SHOW_MUTE_IN_CRASH_DIALOG);
+ dumpSetting(s, p,
+ Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED,
+ GlobalSettingsProto.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED);
+ dumpSetting(s, p,
+ Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
+ GlobalSettingsProto.CHAINED_BATTERY_ATTRIBUTION_ENABLED);
}
/** Dump a single {@link SettingsState.Setting} to a proto buf */
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0b6e11b..1fc36be 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -72,6 +72,7 @@
<uses-permission android:name="android.permission.PEERS_MAC_ADDRESS"/>
<!-- Physical hardware -->
<uses-permission android:name="android.permission.MANAGE_USB" />
+ <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
<uses-permission android:name="android.permission.DEVICE_POWER" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.MASTER_CLEAR" />
@@ -199,6 +200,9 @@
<!-- to access instant apps -->
<uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" />
+ <!-- to control remote app transitions -->
+ <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
+
<!-- to change themes - light or dark -->
<uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" />
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index 903ff72..32eb5d5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -172,5 +172,6 @@
void onScreenOff();
void onShowSafetyWarning(int flags);
void onAccessibilityModeChanged(Boolean showA11yStream);
+ void onConnectedDeviceChanged(String deviceName);
}
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index a648345e..30d1352 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -14,6 +14,7 @@
package com.android.systemui.plugins.qs;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -65,6 +66,18 @@
default void setHasNotifications(boolean hasNotifications) {
}
+ /**
+ * We need this to handle nested scrolling for QS..
+ * Normally we would do this with requestDisallowInterceptTouchEvent, but when both the
+ * scroll containers are using the same touch slop, they try to start scrolling at the
+ * same time and NotificationPanelView wins, this lets QS win.
+ *
+ * TODO: Do this using NestedScroll capabilities.
+ */
+ default boolean onInterceptTouchEvent(MotionEvent event) {
+ return isCustomizing();
+ }
+
@ProvidesInterface(version = HeightListener.VERSION)
interface HeightListener {
int VERSION = 1;
diff --git a/packages/SystemUI/res/drawable/ic_swap.xml b/packages/SystemUI/res/drawable/ic_swap.xml
new file mode 100644
index 0000000..30da2a9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_swap.xml
@@ -0,0 +1,25 @@
+<!--
+ Copyright (C) 2018 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="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorForeground">
+ <path
+ android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"
+ android:fillColor="#FFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/fingerprint_dialog.xml b/packages/SystemUI/res/layout/fingerprint_dialog.xml
index e5f62b3..161f13f 100644
--- a/packages/SystemUI/res/layout/fingerprint_dialog.xml
+++ b/packages/SystemUI/res/layout/fingerprint_dialog.xml
@@ -99,6 +99,7 @@
android:layout_height="@dimen/fingerprint_dialog_fp_icon_size"
android:layout_gravity="center_horizontal"
android:scaleType="centerInside"
+ android:contentDescription="@string/accessibility_fingerprint_dialog_fingerprint_icon"
android:src="@drawable/fingerprint_icon"/>
<TextView
@@ -112,6 +113,8 @@
android:textSize="12sp"
android:visibility="invisible"
android:gravity="center_horizontal"
+ android:accessibilityLiveRegion="polite"
+ android:contentDescription="@string/accessibility_fingerprint_dialog_help_area"
android:textColor="@color/fingerprint_error_message_color"/>
<LinearLayout android:id="@+id/buttonPanel"
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index e0f0ed9..cd3271f 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -13,13 +13,25 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<View
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/quick_qs_status_icons"
android:layout_width="match_parent"
android:layout_height="20dp"
- android:layout_marginBottom="22dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="14dp"
android:layout_below="@id/quick_status_bar_system_icons"
>
-</View>
+ <com.android.systemui.statusbar.phone.StatusIconContainer
+ android:id="@+id/statusIcons"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+ <include layout="@layout/signal_cluster_view"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/signal_cluster_margin_start" />
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index 3590b76..1d1f95b 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -38,23 +38,34 @@
android:maxLines="1"
android:textColor="?android:attr/colorControlNormal"
android:textAppearance="?android:attr/textAppearanceSmall" />
- <TextView
- android:id="@+id/volume_row_connected_device"
- android:visibility="gone"
+ <LinearLayout
+ android:id="@+id/output_chooser"
+ android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:ellipsize="end"
- android:maxLines="1"
- android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" />
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/output_chooser"
- style="@style/VolumeButtons"
+ android:minWidth="48dp"
+ android:minHeight="48dp"
+ android:paddingTop="10dp"
android:background="?android:selectableItemBackgroundBorderless"
- android:layout_width="@dimen/volume_button_size"
- android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:src="@drawable/ic_volume_expand_animation"
- android:soundEffectsEnabled="false" />
+ android:gravity="center">
+ <TextView
+ android:id="@+id/volume_row_connected_device"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" />
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/output_chooser_button"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:background="?android:selectableItemBackgroundBorderless"
+ style="@style/VolumeButtons"
+ android:layout_centerVertical="true"
+ android:src="@drawable/ic_swap"
+ android:soundEffectsEnabled="false" />
+ </LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/volume_row_slider_frame"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7ee9eda..d58c725 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -336,6 +336,8 @@
<dimen name="qs_footer_padding_end">24dp</dimen>
<dimen name="qs_footer_icon_size">16dp</dimen>
+ <dimen name="qs_notif_collapsed_space">64dp</dimen>
+
<!-- Desired qs icon overlay size. -->
<dimen name="qs_detail_icon_overlay_size">24dp</dimen>
diff --git a/packages/SystemUI/res/values/donottranslate.xml b/packages/SystemUI/res/values/donottranslate.xml
index 351a1fd..38f469e 100644
--- a/packages/SystemUI/res/values/donottranslate.xml
+++ b/packages/SystemUI/res/values/donottranslate.xml
@@ -20,4 +20,6 @@
<!-- Date format for display: should match the lockscreen in /policy. -->
<string name="system_ui_date_pattern">@*android:string/system_ui_date_pattern</string>
+ <!-- Date format for the always on display. -->
+ <item type="string" name="system_ui_aod_date_pattern">eeeMMMd</item>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8ea1225..6d61a0c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -259,6 +259,13 @@
<!-- Button name for "Cancel". [CHAR LIMIT=NONE] -->
<string name="cancel">Cancel</string>
+ <!-- Content description of the fingerprint icon when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_fingerprint_dialog_fingerprint_icon">Fingerprint icon</string>
+ <!-- Content description of the application icon when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_fingerprint_dialog_app_icon">Application icon</string>
+ <!-- Content description for the error/help message are when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_fingerprint_dialog_help_area">Help message area</string>
+
<!-- Content description of the compatibility zoom button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_compatibility_zoom_button">Compatibility zoom button.</string>
@@ -1499,6 +1506,10 @@
<string name="notification_channel_disabled">You won\'t see these notifications anymore</string>
<!-- Notification Inline controls: continue receiving notifications prompt, channel level -->
+ <string name="inline_blocking_helper">You usually dismiss these notifications.
+ \nKeep showing them?</string>
+
+ <!-- Notification Inline controls: continue receiving notifications prompt, channel level -->
<string name="inline_keep_showing">Keep showing these notifications?</string>
<!-- Notification inline controls: block notifications button -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index bcce6d1..2497d20 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -306,6 +306,7 @@
<item name="darkIconTheme">@style/DualToneDarkTheme</item>
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
<item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_dark</item>
+ <item name="android:colorError">@*android:color/error_color_material_dark</item>
<item name="android:colorControlHighlight">@*android:color/primary_text_material_dark</item>
<item name="*android:lockPatternStyle">@style/LockPatternStyle</item>
<item name="passwordStyle">@style/PasswordTheme</item>
@@ -317,6 +318,7 @@
<style name="Theme.SystemUI.Light" parent="@*android:style/Theme.DeviceDefault.QuickSettings">
<item name="wallpaperTextColor">@*android:color/primary_text_material_light</item>
<item name="wallpaperTextColorSecondary">@*android:color/secondary_text_material_light</item>
+ <item name="android:colorError">@*android:color/error_color_material_light</item>
<item name="android:colorControlHighlight">@*android:color/primary_text_material_light</item>
<item name="passwordStyle">@style/PasswordTheme.Light</item>
</style>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 2b656c2..e440731 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -330,7 +330,7 @@
public void setPulsing(boolean pulsing) {
mPulsing = pulsing;
- mKeyguardSlice.setVisibility(pulsing ? GONE : VISIBLE);
+ mKeyguardSlice.setVisibility(pulsing ? INVISIBLE : VISIBLE);
onSliceContentChanged(mKeyguardSlice.hasHeader());
updateDozeVisibleViews();
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 3538327..2c0e95b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -61,7 +61,7 @@
*/
public class SystemUIApplication extends Application implements SysUiServiceProvider {
- private static final String TAG = "SystemUIService";
+ public static final String TAG = "SystemUIService";
private static final boolean DEBUG = false;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 11e0f28..ddf0bd0 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -20,11 +20,15 @@
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
+import android.os.Process;
import android.os.SystemProperties;
+import android.util.Slog;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import com.android.internal.os.BinderInternal;
+
public class SystemUIService extends Service {
@Override
@@ -36,6 +40,21 @@
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
throw new RuntimeException();
}
+
+ if (Build.IS_DEBUGGABLE) {
+ // b/71353150 - looking for leaked binder proxies
+ BinderInternal.nSetBinderProxyCountEnabled(true);
+ BinderInternal.nSetBinderProxyCountWatermarks(1000,900);
+ BinderInternal.setBinderProxyCountCallback(
+ new BinderInternal.BinderProxyLimitListener() {
+ @Override
+ public void onLimitReached(int uid) {
+ Slog.w(SystemUIApplication.TAG,
+ "uid " + uid + " sent too many Binder proxies to uid "
+ + Process.myUid());
+ }
+ }, Dependency.get(Dependency.MAIN_HANDLER));
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java
index 19bc2ec..9779937 100644
--- a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java
@@ -146,7 +146,7 @@
subtitle.setText(mBundle.getCharSequence(FingerprintDialog.KEY_SUBTITLE));
description.setText(mBundle.getCharSequence(FingerprintDialog.KEY_DESCRIPTION));
negative.setText(mBundle.getCharSequence(FingerprintDialog.KEY_NEGATIVE_TEXT));
- image.setImageDrawable(getAppIcon());
+ setAppIcon(image);
final CharSequence positiveText =
mBundle.getCharSequence(FingerprintDialog.KEY_POSITIVE_TEXT);
@@ -190,6 +190,7 @@
private void showMessage(String message) {
mHandler.removeMessages(FingerprintDialogImpl.MSG_CLEAR_MESSAGE);
mErrorText.setText(message);
+ mErrorText.setContentDescription(message);
mErrorText.setVisibility(View.VISIBLE);
mHandler.sendMessageDelayed(mHandler.obtainMessage(FingerprintDialogImpl.MSG_CLEAR_MESSAGE),
FingerprintDialog.HIDE_DIALOG_DELAY);
@@ -205,12 +206,16 @@
false /* userCanceled */), FingerprintDialog.HIDE_DIALOG_DELAY);
}
- private Drawable getAppIcon() {
+ private void setAppIcon(ImageView image) {
final ActivityManager.RunningTaskInfo taskInfo = mActivityManagerWrapper.getRunningTask();
final ComponentName cn = taskInfo.topActivity;
final int userId = mActivityManagerWrapper.getCurrentUserId();
final ActivityInfo activityInfo = mPackageManageWrapper.getActivityInfo(cn, userId);
- return mActivityManagerWrapper.getBadgedActivityIcon(activityInfo, userId);
+ image.setImageDrawable(mActivityManagerWrapper.getBadgedActivityIcon(activityInfo, userId));
+ image.setContentDescription(
+ getResources().getString(R.string.accessibility_fingerprint_dialog_app_icon)
+ + " "
+ + mActivityManagerWrapper.getBadgedActivityLabel(activityInfo, userId));
}
public WindowManager.LayoutParams getLayoutParams() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index e49e80d..c7d276c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -118,7 +118,7 @@
public boolean onCreateSliceProvider() {
mNextAlarmController = new NextAlarmControllerImpl(getContext());
mNextAlarmController.addCallback(this);
- mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
+ mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
registerClockUpdate(false /* everyMinute */);
updateClock();
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
index 8f41a60..bd130f4 100644
--- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
@@ -2,7 +2,25 @@
public interface EnhancedEstimates {
+ /**
+ * Returns a boolean indicating if the hybrid notification should be used.
+ */
boolean isHybridNotificationEnabled();
+ /**
+ * Returns an estimate object if the feature is enabled.
+ */
Estimate getEstimate();
+
+ /**
+ * Returns a long indicating the amount of time remaining in milliseconds under which we will
+ * show a regular warning to the user.
+ */
+ long getLowWarningThreshold();
+
+ /**
+ * Returns a long indicating the amount of time remaining in milliseconds under which we will
+ * show a severe warning to the user.
+ */
+ long getSevereWarningThreshold();
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
index d447542..5686d80 100644
--- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
@@ -13,4 +13,14 @@
public Estimate getEstimate() {
return null;
}
+
+ @Override
+ public long getLowWarningThreshold() {
+ return 0;
+ }
+
+ @Override
+ public long getSevereWarningThreshold() {
+ return 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 736286f..aa56694 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -99,6 +99,8 @@
private long mWarningTriggerTimeMs;
private Estimate mEstimate;
+ private long mLowWarningThreshold;
+ private long mSevereWarningThreshold;
private boolean mWarning;
private boolean mPlaySound;
private boolean mInvalidCharger;
@@ -142,11 +144,18 @@
@Override
public void updateEstimate(Estimate estimate) {
mEstimate = estimate;
- if (estimate.estimateMillis <= PowerUI.THREE_HOURS_IN_MILLIS) {
+ if (estimate.estimateMillis <= mLowWarningThreshold) {
mWarningTriggerTimeMs = System.currentTimeMillis();
}
}
+ @Override
+ public void updateThresholds(long lowThreshold, long severeThreshold) {
+ mLowWarningThreshold = lowThreshold;
+ mSevereWarningThreshold = severeThreshold;
+ }
+
+
private void updateNotification() {
if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound="
+ mPlaySound + " mInvalidCharger=" + mInvalidCharger);
@@ -181,7 +190,8 @@
}
protected void showWarningNotification() {
- final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0);
+ final String percentage = NumberFormat.getPercentInstance()
+ .format((double) mBatteryLevel / 100.0);
// get standard notification copy
String title = mContext.getString(R.string.battery_low_title);
@@ -214,7 +224,8 @@
}
// Make the notification red if the percentage goes below a certain amount or the time
// remaining estimate is disabled
- if (mEstimate == null || mBucket < 0) {
+ if (mEstimate == null || mBucket < 0
+ || mEstimate.estimateMillis < mSevereWarningThreshold) {
nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
}
nb.addAction(0,
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index c5aab60..b43e99b 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -269,6 +269,8 @@
if (estimate != null) {
mTimeRemaining = estimate.estimateMillis;
mWarnings.updateEstimate(estimate);
+ mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(),
+ mEnhancedEstimates.getSevereWarningThreshold());
}
}
@@ -292,7 +294,7 @@
&& !isPowerSaver
&& (((bucket < oldBucket || oldPlugged) && bucket < 0)
|| (mEnhancedEstimates.isHybridNotificationEnabled()
- && timeRemaining < THREE_HOURS_IN_MILLIS
+ && timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
&& isHourLess(oldTimeRemaining, timeRemaining)))
&& mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
}
@@ -306,7 +308,7 @@
boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
long timeRemaining, boolean isPowerSaver) {
final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled()
- && timeRemaining > THREE_HOURS_IN_MILLIS;
+ && timeRemaining > mEnhancedEstimates.getLowWarningThreshold();
final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0);
return isPowerSaver
|| plugged
@@ -485,6 +487,7 @@
public interface WarningsUI {
void update(int batteryLevel, int bucket, long screenOffTime);
void updateEstimate(Estimate estimate);
+ void updateThresholds(long lowThreshold, long severeThreshold);
void dismissLowBatteryWarning();
void showLowBatteryWarning(boolean playSound);
void dismissInvalidChargerWarning();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 8f18800..95185c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -244,10 +244,8 @@
mFirstPageDelayedAnimator = new TouchAnimator.Builder()
.setStartDelay(EXPANDED_TILE_DELAY)
.addFloat(tileLayout, "alpha", 0, 1)
- .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
.addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
.addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build();
- mAllViews.add(mQsPanel.getPageIndicator());
mAllViews.add(mQsPanel.getDivider());
mAllViews.add(mQsPanel.getFooter().getView());
float px = 0;
@@ -265,7 +263,6 @@
}
mNonfirstPageAnimator = new TouchAnimator.Builder()
.addFloat(mQuickQsPanel, "alpha", 1, 0)
- .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
.addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
.setListener(mNonFirstPageListener)
.setEndDelay(.5f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 7320b86..6b0d592 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -17,11 +17,11 @@
package com.android.systemui.qs;
import android.content.Context;
+import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
@@ -80,11 +80,22 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ getDisplay().getRealSize(mSizePoint);
+
// Since we control our own bottom, be whatever size we want.
// Otherwise the QSPanel ends up with 0 height when the window is only the
// size of the status bar.
- mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(
- MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED));
+ Configuration config = getResources().getConfiguration();
+ boolean navBelow = config.smallestScreenWidthDp >= 600
+ || config.orientation != Configuration.ORIENTATION_LANDSCAPE;
+ MarginLayoutParams params = (MarginLayoutParams) mQSPanel.getLayoutParams();
+ int maxQs = mSizePoint.y - params.topMargin - params.bottomMargin - getPaddingBottom()
+ - getResources().getDimensionPixelSize(R.dimen.qs_notif_collapsed_space);
+ if (navBelow) {
+ maxQs -= getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
+ }
+ mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
+
int width = mQSPanel.getMeasuredWidth();
LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
int height = layoutParams.topMargin + layoutParams.bottomMargin
@@ -94,7 +105,6 @@
// QSCustomizer will always be the height of the screen, but do this after
// other measuring to avoid changing the height of the QS.
- getDisplay().getRealSize(mSizePoint);
mQSCustomizer.measure(widthMeasureSpec,
MeasureSpec.makeMeasureSpec(mSizePoint.y, MeasureSpec.EXACTLY));
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index cdf0c0f..29a8f16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -25,6 +25,7 @@
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
@@ -207,6 +208,11 @@
}
@Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return isCustomizing() || mQSPanel.onInterceptTouchEvent(event);
+ }
+
+ @Override
public void setHeaderClickable(boolean clickable) {
if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 8f41084..00b6c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -28,8 +28,8 @@
import android.service.quicksettings.Tile;
import android.util.AttributeSet;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
@@ -62,11 +62,8 @@
protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
protected final View mBrightnessView;
private final H mHandler = new H();
- private final View mPageIndicator;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
- private int mPanelPaddingBottom;
- private int mBrightnessPaddingTop;
protected boolean mExpanded;
protected boolean mListening;
@@ -77,6 +74,7 @@
protected QSSecurityFooter mFooter;
private boolean mGridContentVisible = true;
+ private QSScrollLayout mScrollLayout;
protected QSTileLayout mTileLayout;
private QSCustomizer mCustomizePanel;
@@ -95,18 +93,12 @@
setOrientation(VERTICAL);
- mBrightnessView = LayoutInflater.from(context).inflate(
- R.layout.quick_settings_brightness_dialog, this, false);
- addView(mBrightnessView);
-
- setupTileLayout();
-
- mPageIndicator = LayoutInflater.from(context).inflate(
- R.layout.qs_page_indicator, this, false);
- addView(mPageIndicator);
- if (mTileLayout instanceof PagedTileLayout) {
- ((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator);
- }
+ mBrightnessView = LayoutInflater.from(mContext).inflate(
+ R.layout.quick_settings_brightness_dialog, this, false);
+ mTileLayout = new TileLayout(mContext);
+ mTileLayout.setListening(mListening);
+ mScrollLayout = new QSScrollLayout(mContext, mBrightnessView, (View) mTileLayout);
+ addView(mScrollLayout);
addDivider();
@@ -131,17 +123,6 @@
return mDivider;
}
- public View getPageIndicator() {
- return mPageIndicator;
- }
-
- protected void setupTileLayout() {
- mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
- R.layout.qs_paged_tile_layout, this, false);
- mTileLayout.setListening(mListening);
- addView((View) mTileLayout);
- }
-
public boolean isShowingCustomize() {
return mCustomizePanel != null && mCustomizePanel.isCustomizing();
}
@@ -241,9 +222,13 @@
public void updateResources() {
final Resources res = mContext.getResources();
- mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
- mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top);
- setPadding(0, mBrightnessPaddingTop, 0, mPanelPaddingBottom);
+ mBrightnessView.setPadding(
+ mBrightnessView.getPaddingLeft(),
+ res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top),
+ mBrightnessView.getPaddingRight(),
+ mBrightnessView.getPaddingBottom());
+ setPadding(
+ 0, 0, 0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
for (TileRecord r : mRecords) {
r.tile.clearState();
}
@@ -282,8 +267,11 @@
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
- if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
- ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
+ if (!mExpanded) {
+ if (mTileLayout instanceof PagedTileLayout) {
+ ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
+ }
+ mScrollLayout.setScrollY(0);
}
mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
if (!mExpanded) {
@@ -317,6 +305,7 @@
}
public void refreshAllTiles() {
+ mBrightnessController.checkRestrictionAndSetEnabled();
for (TileRecord r : mRecords) {
r.tile.refreshState();
}
@@ -564,6 +553,11 @@
mFooter.showDeviceMonitoringDialog();
}
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ return mExpanded && mScrollLayout.shouldIntercept(event);
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
new file mode 100644
index 0000000..9a74787
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 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;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.v4.widget.NestedScrollView;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+import android.widget.LinearLayout;
+
+/**
+ * Quick setting scroll view containing the brightness slider and the QS tiles.
+ *
+ * <p>Call {@link #shouldIntercept(MotionEvent)} from parent views'
+ * {@link #onInterceptTouchEvent(MotionEvent)} method to determine whether this view should
+ * consume the touch event.
+ */
+public class QSScrollLayout extends NestedScrollView {
+ private final int mTouchSlop;
+ private int mLastMotionY;
+ private Rect mHitRect = new Rect();
+
+ public QSScrollLayout(Context context, View... children) {
+ super(context);
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ LinearLayout linearLayout = new LinearLayout(mContext);
+ linearLayout.setLayoutParams(new LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.WRAP_CONTENT));
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+ for (View view : children) {
+ linearLayout.addView(view);
+ }
+ addView(linearLayout);
+ }
+
+ public boolean shouldIntercept(MotionEvent ev) {
+ getHitRect(mHitRect);
+ if (!mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+ // Do not intercept touches that are not within this view's bounds.
+ return false;
+ }
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mLastMotionY = (int) ev.getY();
+ } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ // Do not allow NotificationPanelView to intercept touch events when this
+ // view can be scrolled down.
+ if (mLastMotionY >= 0 && Math.abs(ev.getY() - mLastMotionY) > mTouchSlop
+ && canScrollVertically(1)) {
+ requestParentDisallowInterceptTouchEvent(true);
+ mLastMotionY = (int) ev.getY();
+ return true;
+ }
+ } else if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL
+ || ev.getActionMasked() == MotionEvent.ACTION_UP) {
+ mLastMotionY = -1;
+ requestParentDisallowInterceptTouchEvent(false);
+ }
+ return false;
+ }
+
+ private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ final ViewParent parent = getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(disallowIntercept);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 947b23f..8314855 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -20,6 +20,7 @@
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Space;
@@ -50,13 +51,14 @@
public QuickQSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
if (mFooter != null) {
- removeView((View) mFooter.getView());
+ removeView(mFooter.getView());
}
if (mTileLayout != null) {
for (int i = 0; i < mRecords.size(); i++) {
mTileLayout.removeTile(mRecords.get(i));
}
- removeView((View) mTileLayout);
+ View tileLayoutView = (View) mTileLayout;
+ ((ViewGroup) tileLayoutView.getParent()).removeView(tileLayoutView);
}
mTileLayout = new HeaderTileLayout(context);
mTileLayout.setListening(mListening);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index a97b35c..17ede65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -39,7 +39,8 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSDetail.Callback;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.SignalClusterView;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager;
import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
@@ -56,6 +57,10 @@
protected QuickQSPanel mHeaderQsPanel;
protected QSTileHost mHost;
+ private TintedIconManager mIconManager;
+ private TouchAnimator mAlphaAnimator;
+
+ private View mQuickQsStatusIcons;
private View mDate;
@@ -71,16 +76,25 @@
mHeaderQsPanel = findViewById(R.id.quick_qs_panel);
mDate = findViewById(R.id.date);
mDate.setOnClickListener(this);
+ mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons);
+ mIconManager = new TintedIconManager(findViewById(R.id.statusIcons));
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
updateResources();
- // Set light text on the header icons because they will always be on a black background
- int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
Rect tintArea = new Rect(0, 0, 0, 0);
+ int colorForeground = Utils.getColorAttr(getContext(), android.R.attr.colorForeground);
+ float intensity = colorForeground == Color.WHITE ? 0 : 1;
+ int fillColor = fillColorForIntensity(intensity, getContext());
+
+ // Set light text on the header icons because they will always be on a black background
applyDarkness(R.id.clock, tintArea, 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
+ applyDarkness(id.signal_cluster, tintArea, intensity, colorForeground);
+
+ // Set the correct tint for the status icons so they contrast
+ mIconManager.setTint(fillColor);
BatteryMeterView battery = findViewById(R.id.battery);
battery.setFillColor(Color.WHITE);
@@ -96,6 +110,13 @@
}
}
+ private int fillColorForIntensity(float intensity, Context context) {
+ if (intensity == 0) {
+ return context.getColor(R.color.light_mode_icon_color_dual_tone_fill);
+ }
+ return context.getColor(R.color.dark_mode_icon_color_dual_tone_fill);
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -109,6 +130,13 @@
}
private void updateResources() {
+ updateAlphaAnimator();
+ }
+
+ private void updateAlphaAnimator() {
+ mAlphaAnimator = new TouchAnimator.Builder()
+ .addFloat(mQuickQsStatusIcons, "alpha", 1, 0)
+ .build();
}
public int getCollapsedHeight() {
@@ -127,6 +155,9 @@
}
public void setExpansion(float headerExpansionFraction) {
+ if (mAlphaAnimator != null ) {
+ mAlphaAnimator.setPosition(headerExpansionFraction);
+ }
}
@Override
@@ -142,6 +173,7 @@
@Override
public void onAttachedToWindow() {
SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).addCallbacks(this);
+ Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager);
}
@Override
@@ -149,17 +181,10 @@
public void onDetachedFromWindow() {
setListening(false);
SysUiServiceProvider.getComponent(getContext(), CommandQueue.class).removeCallbacks(this);
+ Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager);
super.onDetachedFromWindow();
}
- @Override
- public void onClick(View v) {
- if (v == mDate) {
- Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent(
- AlarmClock.ACTION_SHOW_ALARMS), 0);
- }
- }
-
public void setListening(boolean listening) {
if (listening == mListening) {
return;
@@ -168,6 +193,14 @@
mListening = listening;
}
+ @Override
+ public void onClick(View v) {
+ if(v == mDate){
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent(
+ AlarmClock.ACTION_SHOW_ALARMS),0);
+ }
+ }
+
public void updateEverything() {
post(() -> setClickable(false));
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 2ee66d8..eb2e519 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -19,6 +19,9 @@
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
+import android.app.AlarmManager;
+import android.app.AlarmManager.AlarmClockInfo;
+import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -27,9 +30,11 @@
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.net.Uri;
import android.os.UserManager;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.service.notification.ScheduleCalendar;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ZenRule;
import android.service.quicksettings.Tile;
@@ -38,11 +43,13 @@
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
+import android.view.WindowManager;
import android.widget.Switch;
import android.widget.Toast;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.notification.EnableZenModeDialog;
import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
@@ -53,6 +60,7 @@
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.volume.ZenModePanel;
@@ -129,14 +137,28 @@
@Override
protected void handleClick() {
+ // Zen is currently on
if (mState.value) {
mController.setZen(ZEN_MODE_OFF, null, TAG);
} else {
- mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
+ showDetail(true);
}
}
@Override
+ public void showDetail(boolean show) {
+ mUiHandler.post(() -> {
+ Dialog mDialog = new EnableZenModeDialog(mContext).createDialog();
+ mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ SystemUIDialog.setShowForAllUsers(mDialog, true);
+ SystemUIDialog.registerDismissListener(mDialog);
+ SystemUIDialog.setWindowOnTop(mDialog);
+ mUiHandler.post(() -> mDialog.show());
+ mHost.collapsePanels();
+ });
+ }
+
+ @Override
protected void handleSecondaryClick() {
if (mController.isVolumeRestricted()) {
// Collapse the panels, so the user can see the toast.
@@ -178,6 +200,7 @@
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.slash.isSlashed = !state.value;
state.label = getTileLabel();
+ state.secondaryLabel = getSecondaryLabel(zen != Global.ZEN_MODE_OFF);
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
switch (zen) {
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
@@ -209,6 +232,102 @@
state.expandedAccessibilityClassName = Switch.class.getName();
}
+ /**
+ * Returns the secondary label to use for the given instance of do not disturb.
+ * - If turned on manually and end time is known, returns end time.
+ * - If turned on by an automatic rule, returns the automatic rule name.
+ * - If on due to an app, returns the app name.
+ * - If there's a combination of rules/apps that trigger, then shows the one that will
+ * last the longest if applicable.
+ * @return null if do not disturb is off.
+ */
+ private String getSecondaryLabel(boolean zenOn) {
+ if (!zenOn) {
+ return null;
+ }
+
+ ZenModeConfig config = mController.getConfig();
+ String secondaryText = "";
+ long latestEndTime = -1;
+
+ // DND turned on by manual rule
+ if (config.manualRule != null) {
+ final Uri id = config.manualRule.conditionId;
+ if (config.manualRule.enabler != null) {
+ // app triggered manual rule
+ String appName = ZenModeConfig.getOwnerCaption(mContext, config.manualRule.enabler);
+ if (!appName.isEmpty()) {
+ secondaryText = appName;
+ }
+ } else {
+ if (id == null) {
+ // Do not disturb manually triggered to remain on forever until turned off
+ // No subtext
+ return null;
+ } else {
+ latestEndTime = ZenModeConfig.tryParseCountdownConditionId(id);
+ if (latestEndTime > 0) {
+ final CharSequence formattedTime = ZenModeConfig.getFormattedTime(mContext,
+ latestEndTime, ZenModeConfig.isToday(latestEndTime),
+ mContext.getUserId());
+ secondaryText = mContext.getString(R.string.qs_dnd_until, formattedTime);
+ }
+ }
+ }
+ }
+
+ // DND turned on by an automatic rule
+ for (ZenModeConfig.ZenRule automaticRule : config.automaticRules.values()) {
+ if (automaticRule.isAutomaticActive()) {
+ if (ZenModeConfig.isValidEventConditionId(automaticRule.conditionId) ||
+ ZenModeConfig.isValidScheduleConditionId(automaticRule.conditionId)) {
+ // set text if automatic rule end time is the latest active rule end time
+ long endTime = parseAutomaticRuleEndTime(automaticRule.conditionId);
+ if (endTime > latestEndTime) {
+ latestEndTime = endTime;
+ secondaryText = automaticRule.name;
+ }
+ } else {
+ // set text if 3rd party rule
+ return automaticRule.name;
+ }
+ }
+ }
+
+ return !secondaryText.equals("") ? secondaryText : null;
+ }
+
+ private long parseAutomaticRuleEndTime(Uri id) {
+ if (ZenModeConfig.isValidEventConditionId(id)) {
+ // cannot look up end times for events
+ return Long.MAX_VALUE;
+ }
+
+ if (ZenModeConfig.isValidScheduleConditionId(id)) {
+ ScheduleCalendar schedule = ZenModeConfig.toScheduleCalendar(id);
+ long endTimeMs = schedule.getNextChangeTime(System.currentTimeMillis());
+
+ // check if automatic rule will end on next alarm
+ if (schedule.exitAtAlarm()) {
+ long nextAlarm = getNextAlarm(mContext);
+ schedule.maybeSetNextAlarm(System.currentTimeMillis(), nextAlarm);
+ if (schedule.shouldExitForAlarm(endTimeMs)) {
+ return nextAlarm;
+ }
+ }
+
+ return endTimeMs;
+ }
+
+ return -1;
+ }
+
+ private long getNextAlarm(Context context) {
+ final AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ final AlarmClockInfo info = alarms.getNextAlarmClock(mContext.getUserId());
+ return info != null ? info.getTriggerTime() : 0;
+ }
+
@Override
public int getMetricsCategory() {
return MetricsEvent.QS_DND;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 99a9be3..ea6e174 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -39,7 +39,7 @@
* Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the
* nearest hour and add on the AM/PM indicator.
*/
- private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "H a";
+ private static final String APPROXIMATE_HOUR_DATE_TIME_PATTERN = "h a";
private ColorDisplayController mController;
private boolean mIsListening;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
index 6c553de..0494e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
@@ -150,6 +150,11 @@
}
public void onConnectedToLauncher(ComponentName launcherComponent) {
+ // TODO: re-enable this once we have the proper callback for when a swipe up was performed.
+ final boolean disableOnboarding = true;
+ if (disableOnboarding) {
+ return;
+ }
mLauncherComponent = launcherComponent;
boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext,
Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
index d3f997a..15e92f4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
@@ -16,9 +16,11 @@
package com.android.systemui.settings;
+import android.animation.ValueAnimator;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
@@ -29,6 +31,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
@@ -37,6 +40,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.Dependency;
import java.util.ArrayList;
@@ -45,11 +49,7 @@
private static final String TAG = "StatusBar.BrightnessController";
private static final boolean SHOW_AUTOMATIC_ICON = false;
- /**
- * {@link android.provider.Settings.System#SCREEN_AUTO_BRIGHTNESS_ADJ} uses the range [-1, 1].
- * Using this factor, it is converted to [0, BRIGHTNESS_ADJ_RESOLUTION] for the SeekBar.
- */
- private static final float BRIGHTNESS_ADJ_RESOLUTION = 2048;
+ private static final int SLIDER_ANIMATION_DURATION = 3000;
private static final int MSG_UPDATE_ICON = 0;
private static final int MSG_UPDATE_SLIDER = 1;
@@ -67,7 +67,7 @@
private final ImageView mIcon;
private final ToggleSlider mControl;
private final boolean mAutomaticAvailable;
- private final IPowerManager mPower;
+ private final DisplayManager mDisplayManager;
private final CurrentUserTracker mUserTracker;
private final IVrManager mVrManager;
@@ -81,6 +81,9 @@
private volatile boolean mIsVrModeEnabled;
private boolean mListening;
private boolean mExternalChange;
+ private boolean mControlInitialized;
+
+ private ValueAnimator mSliderAnimator;
public interface BrightnessStateChangeCallback {
public void onBrightnessLevelChanged();
@@ -95,8 +98,6 @@
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
private final Uri BRIGHTNESS_FOR_VR_URI =
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR);
- private final Uri BRIGHTNESS_ADJ_URI =
- Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ);
public BrightnessObserver(Handler handler) {
super(handler);
@@ -114,12 +115,10 @@
if (BRIGHTNESS_MODE_URI.equals(uri)) {
mBackgroundHandler.post(mUpdateModeRunnable);
mBackgroundHandler.post(mUpdateSliderRunnable);
- } else if (BRIGHTNESS_URI.equals(uri) && !mAutomatic) {
+ } else if (BRIGHTNESS_URI.equals(uri)) {
mBackgroundHandler.post(mUpdateSliderRunnable);
} else if (BRIGHTNESS_FOR_VR_URI.equals(uri)) {
mBackgroundHandler.post(mUpdateSliderRunnable);
- } else if (BRIGHTNESS_ADJ_URI.equals(uri) && mAutomatic) {
- mBackgroundHandler.post(mUpdateSliderRunnable);
} else {
mBackgroundHandler.post(mUpdateModeRunnable);
mBackgroundHandler.post(mUpdateSliderRunnable);
@@ -141,9 +140,6 @@
cr.registerContentObserver(
BRIGHTNESS_FOR_VR_URI,
false, this, UserHandle.USER_ALL);
- cr.registerContentObserver(
- BRIGHTNESS_ADJ_URI,
- false, this, UserHandle.USER_ALL);
}
public void stopObserving() {
@@ -214,12 +210,6 @@
mHandler.obtainMessage(MSG_UPDATE_SLIDER,
mMaximumBacklightForVr - mMinimumBacklightForVr,
value - mMinimumBacklightForVr).sendToTarget();
- } else if (mAutomatic) {
- float value = Settings.System.getFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0,
- UserHandle.USER_CURRENT);
- mHandler.obtainMessage(MSG_UPDATE_SLIDER, (int) BRIGHTNESS_ADJ_RESOLUTION,
- (int) ((value + 1) * BRIGHTNESS_ADJ_RESOLUTION / 2f)).sendToTarget();
} else {
int value;
value = Settings.System.getIntForUser(mContext.getContentResolver(),
@@ -250,7 +240,7 @@
break;
case MSG_UPDATE_SLIDER:
mControl.setMax(msg.arg1);
- mControl.setValue(msg.arg2);
+ animateSliderTo(msg.arg2);
break;
case MSG_SET_CHECKED:
mControl.setChecked(msg.arg1 != 0);
@@ -295,8 +285,7 @@
mAutomaticAvailable = context.getResources().getBoolean(
com.android.internal.R.bool.config_automatic_brightness_available);
- mPower = IPowerManager.Stub.asInterface(ServiceManager.getService(
- Context.POWER_SERVICE));
+ mDisplayManager = context.getSystemService(DisplayManager.class);
mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
Context.VR_SERVICE));
}
@@ -356,6 +345,10 @@
updateIcon(mAutomatic);
if (mExternalChange) return;
+ if (mSliderAnimator != null) {
+ mSliderAnimator.cancel();
+ }
+
if (mIsVrModeEnabled) {
final int val = value + mMinimumBacklightForVr;
if (stopTracking) {
@@ -371,7 +364,7 @@
}
});
}
- } else if (!mAutomatic) {
+ } else {
final int val = value + mMinimumBacklight;
if (stopTracking) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_BRIGHTNESS, val);
@@ -386,21 +379,6 @@
}
});
}
- } else {
- final float adj = value / (BRIGHTNESS_ADJ_RESOLUTION / 2f) - 1;
- if (stopTracking) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_BRIGHTNESS_AUTO, value);
- }
- setBrightnessAdj(adj);
- if (!tracking) {
- AsyncTask.execute(new Runnable() {
- public void run() {
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adj,
- UserHandle.USER_CURRENT);
- }
- });
- }
}
for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
@@ -408,6 +386,18 @@
}
}
+ public void checkRestrictionAndSetEnabled() {
+ mBackgroundHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ((ToggleSliderView)mControl).setEnforcedAdmin(
+ RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
+ UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+ mUserTracker.getCurrentUserId()));
+ }
+ });
+ }
+
private void setMode(int mode) {
Settings.System.putIntForUser(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS_MODE, mode,
@@ -415,17 +405,11 @@
}
private void setBrightness(int brightness) {
- try {
- mPower.setTemporaryScreenBrightnessSettingOverride(brightness);
- } catch (RemoteException ex) {
- }
+ mDisplayManager.setTemporaryBrightness(brightness);
}
private void setBrightnessAdj(float adj) {
- try {
- mPower.setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(adj);
- } catch (RemoteException ex) {
- }
+ mDisplayManager.setTemporaryAutoBrightnessAdjustment(adj);
}
private void updateIcon(boolean automatic) {
@@ -442,4 +426,23 @@
mBackgroundHandler.post(mUpdateSliderRunnable);
}
}
+
+ private void animateSliderTo(int target) {
+ if (!mControlInitialized) {
+ // Don't animate the first value since it's default state isn't meaningful to users.
+ mControl.setValue(target);
+ mControlInitialized = true;
+ }
+ if (mSliderAnimator != null && mSliderAnimator.isStarted()) {
+ mSliderAnimator.cancel();
+ }
+ mSliderAnimator = ValueAnimator.ofInt(mControl.getValue(), target);
+ mSliderAnimator.addUpdateListener((ValueAnimator animation) -> {
+ mExternalChange = true;
+ mControl.setValue((int)animation.getAnimatedValue());
+ mExternalChange = false;
+ });
+ mSliderAnimator.setDuration(SLIDER_ANIMATION_DURATION);
+ mSliderAnimator.start();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
index 722aba5..8ed4c75 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
@@ -17,14 +17,21 @@
package com.android.systemui.settings;
import android.content.Context;
+import android.content.Intent;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.plugins.ActivityStarter;
+
public class ToggleSeekBar extends SeekBar {
private String mAccessibilityLabel;
+ private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
+
public ToggleSeekBar(Context context) {
super(context);
}
@@ -39,6 +46,12 @@
@Override
public boolean onTouchEvent(MotionEvent event) {
+ if (mEnforcedAdmin != null) {
+ Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
+ mContext, mEnforcedAdmin);
+ Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
+ return true;
+ }
if (!isEnabled()) {
setEnabled(true);
}
@@ -57,4 +70,8 @@
info.setText(mAccessibilityLabel);
}
}
+
+ public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
+ mEnforcedAdmin = admin;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java
index 62abf3d..135f89d 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSlider.java
@@ -28,4 +28,5 @@
default boolean isChecked() { return false; }
void setMax(int max);
void setValue(int value);
+ int getValue();
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java
index 5b234e9..90744a6 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java
@@ -29,6 +29,7 @@
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
+import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -95,6 +96,12 @@
}
}
+ public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
+ mToggle.setEnabled(admin == null);
+ mSlider.setEnabled(admin == null);
+ mSlider.setEnforcedAdmin(admin);
+ }
+
public void setOnChangedListener(Listener l) {
mListener = l;
}
@@ -126,6 +133,11 @@
}
@Override
+ public int getValue() {
+ return mSlider.getProgress();
+ }
+
+ @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mMirror != null) {
MotionEvent copy = ev.copy();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index e59c703..eb5619b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -122,7 +122,7 @@
private Interpolator mCurrentAppearInterpolator;
private Interpolator mCurrentAlphaInterpolator;
- private NotificationBackgroundView mBackgroundNormal;
+ protected NotificationBackgroundView mBackgroundNormal;
private NotificationBackgroundView mBackgroundDimmed;
private ObjectAnimator mBackgroundAnimator;
private RectF mAppearAnimationRect = new RectF();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 5f4854a..1056ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar;
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
import android.animation.Animator;
@@ -37,6 +38,7 @@
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.FloatProperty;
+import android.util.MathUtils;
import android.util.Property;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -67,6 +69,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.NotificationGuts.GutsContent;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -75,6 +78,7 @@
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.stack.AmbientState;
import com.android.systemui.statusbar.stack.AnimationProperties;
import com.android.systemui.statusbar.stack.ExpandableViewState;
import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
@@ -113,6 +117,7 @@
private int mNotificationMaxHeight;
private int mNotificationAmbientHeight;
private int mIncreasedPaddingBetweenElements;
+ private int mNotificationLaunchHeight;
private boolean mMustStayOnScreen;
/** Does this row contain layouts that can adapt to row expansion */
@@ -172,6 +177,7 @@
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
private FalsingManager mFalsingManager;
+ private boolean mExpandAnimationRunning;
private AboveShelfChangedListener mAboveShelfChangedListener;
private HeadsUpManager mHeadsUpManager;
private View mHelperButton;
@@ -270,6 +276,7 @@
private float mTranslationWhenRemoved;
private boolean mWasChildInGroupWhenRemoved;
private int mNotificationColorAmbient;
+ private NotificationViewState mNotificationViewState;
@Override
public boolean isGroupExpansionChanging() {
@@ -363,6 +370,14 @@
mNotificationInflater.inflateNotificationViews();
}
+ @Override
+ public void setPressed(boolean pressed) {
+ if (isOnKeyguard() || mEntry.notification.getNotification().contentIntent == null) {
+ // We're dropping the ripple if we have a collapse / launch animation
+ super.setPressed(pressed);
+ }
+ }
+
public void onNotificationUpdated() {
for (NotificationContentView l : mLayouts) {
l.onNotificationUpdated(mEntry);
@@ -1096,9 +1111,19 @@
return mGroupParentWhenDismissed;
}
- public void performDismiss() {
- if (mOnDismissRunnable != null) {
- mOnDismissRunnable.run();
+ public void performDismiss(boolean fromAccessibility) {
+ if (mGroupManager.isOnlyChildInGroup(getStatusBarNotification())) {
+ ExpandableNotificationRow groupSummary =
+ mGroupManager.getLogicalGroupSummary(getStatusBarNotification());
+ if (groupSummary.isClearable()) {
+ groupSummary.performDismiss(fromAccessibility);
+ }
+ }
+ setDismissed(true, fromAccessibility);
+ if (isClearable()) {
+ if (mOnDismissRunnable != null) {
+ mOnDismissRunnable.run();
+ }
}
}
@@ -1159,6 +1184,9 @@
}
private void updateContentTransformation() {
+ if (mExpandAnimationRunning) {
+ return;
+ }
float contentAlpha;
float translationY = -mContentTransformationAmount * mIconTransformContentShift;
if (mIsLastChild) {
@@ -1440,6 +1468,11 @@
mMenuRow.resetMenu();
}
+ public CharSequence getActiveRemoteInputText() {
+ return mPrivateLayout.getActiveRemoteInputText();
+ }
+
+
public void animateTranslateNotification(final float leftTarget) {
if (mTranslateAnim != null) {
mTranslateAnim.cancel();
@@ -1527,10 +1560,13 @@
}
private void updateChildrenVisibility() {
- mPrivateLayout.setVisibility(!shouldShowPublic() && !mIsSummaryWithChildren ? VISIBLE
- : INVISIBLE);
+ boolean hideContentWhileLaunching = mExpandAnimationRunning && mGuts != null
+ && mGuts.isExposed();
+ mPrivateLayout.setVisibility(!shouldShowPublic() && !mIsSummaryWithChildren
+ && !hideContentWhileLaunching ? VISIBLE : INVISIBLE);
if (mChildrenContainer != null) {
- mChildrenContainer.setVisibility(!shouldShowPublic() && mIsSummaryWithChildren ? VISIBLE
+ mChildrenContainer.setVisibility(!shouldShowPublic() && mIsSummaryWithChildren
+ && !hideContentWhileLaunching ? VISIBLE
: INVISIBLE);
}
// The limits might have changed if the view suddenly became a group or vice versa
@@ -1569,6 +1605,62 @@
updateShelfIconColor();
}
+ public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+ if (params == null) {
+ return;
+ }
+ setTranslationY(params.getTop());
+ float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ params.getProgress(0, 50));
+ float translationZ = MathUtils.lerp(params.getStartTranslationZ(),
+ mNotificationLaunchHeight,
+ zProgress);
+ setTranslationZ(translationZ);
+ setActualHeight(params.getHeight());
+ mBackgroundNormal.setExpandAnimationParams(params);
+ }
+
+ public void setExpandAnimationRunning(boolean expandAnimationRunning) {
+ if (expandAnimationRunning) {
+ View contentView;
+ if (mIsSummaryWithChildren) {
+ contentView = mChildrenContainer;
+ } else {
+ contentView = getShowingLayout();
+ }
+ if (mGuts != null && mGuts.isExposed()) {
+ contentView = mGuts;
+ }
+ contentView.animate()
+ .alpha(0f)
+ .setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT)
+ .setInterpolator(Interpolators.ALPHA_OUT);
+ setAboveShelf(true);
+ mExpandAnimationRunning = true;
+ mNotificationViewState.cancelAnimations(this);
+ mNotificationLaunchHeight = AmbientState.getNotificationLaunchHeight(getContext());
+ } else {
+ mExpandAnimationRunning = false;
+ setAboveShelf(isAboveShelf());
+ if (mGuts != null) {
+ mGuts.setAlpha(1.0f);
+ }
+ }
+ updateChildrenVisibility();
+ updateClipping();
+ mBackgroundNormal.setExpandAnimationRunning(expandAnimationRunning);
+ }
+
+ @Override
+ protected boolean shouldClipToActualHeight() {
+ return super.shouldClipToActualHeight() && !mExpandAnimationRunning;
+ }
+
+ @Override
+ public boolean isExpandAnimationRunning() {
+ return mExpandAnimationRunning;
+ }
+
/**
* Tap sounds should not be played when we're unlocking.
* Doing so would cause audio collision and the system would feel unpolished.
@@ -2142,6 +2234,9 @@
@Override
public void setClipBottomAmount(int clipBottomAmount) {
+ if (mExpandAnimationRunning) {
+ return;
+ }
if (clipBottomAmount != mClipBottomAmount) {
super.setClipBottomAmount(clipBottomAmount);
for (NotificationContentView l : mLayouts) {
@@ -2328,8 +2423,7 @@
}
switch (action) {
case AccessibilityNodeInfo.ACTION_DISMISS:
- NotificationStackScrollLayout.performDismiss(this, mGroupManager,
- true /* fromAccessibility */);
+ performDismiss(true /* fromAccessibility */);
return true;
case AccessibilityNodeInfo.ACTION_COLLAPSE:
case AccessibilityNodeInfo.ACTION_EXPAND:
@@ -2352,13 +2446,15 @@
@Override
public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
- return new NotificationViewState(stackScrollState);
+ mNotificationViewState = new NotificationViewState(stackScrollState);
+ return mNotificationViewState;
}
@Override
public boolean isAboveShelf() {
return !isOnKeyguard()
- && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf));
+ && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)
+ || mExpandAnimationRunning);
}
public void setShowAmbient(boolean showAmbient) {
@@ -2444,9 +2540,12 @@
@Override
public void applyToView(View view) {
- super.applyToView(view);
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (row.isExpandAnimationRunning()) {
+ return;
+ }
+ super.applyToView(view);
row.applyChildrenState(mOverallState);
}
}
@@ -2464,9 +2563,12 @@
@Override
public void animateTo(View child, AnimationProperties properties) {
- super.animateTo(child, properties);
if (child instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (row.isExpandAnimationRunning()) {
+ return;
+ }
+ super.animateTo(child, properties);
row.startChildAnimation(mOverallState, properties);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index eafa825..1496a41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -151,6 +151,10 @@
return mActualHeight;
}
+ public boolean isExpandAnimationRunning() {
+ return false;
+ }
+
/**
* @return The maximum height of this notification.
*/
@@ -375,8 +379,8 @@
return false;
}
- private void updateClipping() {
- if (mClipToActualHeight) {
+ protected void updateClipping() {
+ if (mClipToActualHeight && shouldClipToActualHeight()) {
int top = getClipTopAmount();
mClipRect.set(0, top, getWidth(), Math.max(getActualHeight() + getExtraBottomPadding()
- mClipBottomAmount, top));
@@ -386,6 +390,10 @@
}
}
+ protected boolean shouldClipToActualHeight() {
+ return true;
+ }
+
public void setClipToActualHeight(boolean clipToActualHeight) {
mClipToActualHeight = clipToActualHeight;
updateClipping();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index 45b35d0..d6beb7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -20,6 +20,7 @@
import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
@@ -27,7 +28,9 @@
import android.util.AttributeSet;
import android.view.View;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
/**
* A view that can be used for both the dimmed and normal background of an notification.
@@ -44,6 +47,9 @@
private boolean mBottomIsRounded;
private int mBackgroundTop;
private boolean mBottomAmountClips = true;
+ private boolean mExpandAnimationRunning;
+ private float mActualWidth;
+ private int mDrawableAlpha = 255;
public NotificationBackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -53,9 +59,12 @@
@Override
protected void onDraw(Canvas canvas) {
- if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop) {
+ if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop
+ || mExpandAnimationRunning) {
canvas.save();
- canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+ if (!mExpandAnimationRunning) {
+ canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+ }
draw(canvas, mBackground);
canvas.restore();
}
@@ -64,10 +73,16 @@
private void draw(Canvas canvas, Drawable drawable) {
if (drawable != null) {
int bottom = mActualHeight;
- if (mBottomIsRounded && mBottomAmountClips) {
+ if (mBottomIsRounded && mBottomAmountClips && !mExpandAnimationRunning) {
bottom -= mClipBottomAmount;
}
- drawable.setBounds(0, mBackgroundTop, getWidth(), bottom);
+ int left = 0;
+ int right = getWidth();
+ if (mExpandAnimationRunning) {
+ left = (int) ((getWidth() - mActualWidth) / 2.0f);
+ right = (int) (left + mActualWidth);
+ }
+ drawable.setBounds(left, mBackgroundTop, right, bottom);
drawable.draw(canvas);
}
}
@@ -133,6 +148,9 @@
}
public void setActualHeight(int actualHeight) {
+ if (mExpandAnimationRunning) {
+ return;
+ }
mActualHeight = actualHeight;
invalidate();
}
@@ -170,6 +188,10 @@
}
public void setDrawableAlpha(int drawableAlpha) {
+ mDrawableAlpha = drawableAlpha;
+ if (mExpandAnimationRunning) {
+ return;
+ }
mBackground.setAlpha(drawableAlpha);
}
@@ -208,4 +230,29 @@
mBackgroundTop = backgroundTop;
invalidate();
}
+
+ public void setExpandAnimationParams(ActivityLaunchAnimator.ExpandAnimationParameters params) {
+ mActualHeight = params.getHeight();
+ mActualWidth = params.getWidth();
+ float alphaProgress = Interpolators.ALPHA_IN.getInterpolation(
+ params.getProgress(
+ ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT /* delay */,
+ ActivityLaunchAnimator.ANIMATION_DURATION_FADE_APP /* duration */));
+ mBackground.setAlpha((int) (mDrawableAlpha * (1.0f - alphaProgress)));
+ invalidate();
+ }
+
+ public void setExpandAnimationRunning(boolean running) {
+ mExpandAnimationRunning = running;
+ if (mBackground instanceof LayerDrawable) {
+ GradientDrawable gradientDrawable =
+ (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
+ gradientDrawable.setXfermode(
+ running ? new PorterDuffXfermode(PorterDuff.Mode.SRC) : null);
+ }
+ if (!mExpandAnimationRunning) {
+ setDrawableAlpha(mDrawableAlpha);
+ }
+ invalidate();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index a4c17e3..e811ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -1563,4 +1563,14 @@
}
return visibleWrapper.shouldClipToRounding(topRounded, bottomRounded);
}
+
+ public CharSequence getActiveRemoteInputText() {
+ if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) {
+ return mExpandedRemoteInput.getText();
+ }
+ if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) {
+ return mHeadsUpRemoteInput.getText();
+ }
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
index 9d8892d..1aaa3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -15,6 +15,9 @@
*/
package com.android.systemui.statusbar;
+import static android.service.notification.NotificationListenerService.Ranking
+ .USER_SENTIMENT_NEGATIVE;
+
import android.app.INotificationManager;
import android.app.NotificationChannel;
import android.content.Context;
@@ -41,7 +44,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -133,14 +135,14 @@
* channel.
*/
private void startAppNotificationSettingsActivity(String packageName, final int appUid,
- final NotificationChannel channel) {
+ final NotificationChannel channel, ExpandableNotificationRow row) {
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
intent.putExtra(Settings.EXTRA_APP_UID, appUid);
if (channel != null) {
intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
}
- mPresenter.startNotificationGutsIntent(intent, appUid);
+ mPresenter.startNotificationGutsIntent(intent, appUid, row);
}
public void bindGuts(final ExpandableNotificationRow row) {
@@ -198,14 +200,14 @@
mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
guts.resetFalsingCheck();
mOnSettingsClickListener.onClick(sbn.getKey());
- startAppNotificationSettingsActivity(pkg, appUid, channel);
+ startAppNotificationSettingsActivity(pkg, appUid, channel, row);
};
}
final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
Intent intent) -> {
mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
guts.resetFalsingCheck();
- mPresenter.startNotificationGutsIntent(intent, sbn.getUid());
+ mPresenter.startNotificationGutsIntent(intent, sbn.getUid(), row);
};
final View.OnClickListener onDoneClick = (View v) -> {
saveAndCloseNotificationMenu(row, guts, v);
@@ -231,7 +233,8 @@
try {
info.bindNotification(pmUser, iNotificationManager, pkg, row.getEntry().channel,
channels.size(), sbn, mCheckSaveListener, onSettingsClick,
- onAppSettingsClick, mNonBlockablePkgs);
+ onAppSettingsClick, mNonBlockablePkgs,
+ row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE);
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 6279fdc..735f4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -79,6 +79,7 @@
private OnSettingsClickListener mOnSettingsClickListener;
private OnAppSettingsClickListener mAppSettingsClickListener;
private NotificationGuts mGutsContainer;
+ private boolean mNegativeUserSentiment;
private OnClickListener mOnKeepShowing = v -> {
closeControls(v);
@@ -122,6 +123,22 @@
final OnAppSettingsClickListener onAppSettingsClick,
final Set<String> nonBlockablePkgs)
throws RemoteException {
+ bindNotification(pm, iNotificationManager, pkg, notificationChannel, numChannels, sbn,
+ checkSaveListener, onSettingsClick, onAppSettingsClick, nonBlockablePkgs,
+ false /* negative sentiment */);
+ }
+
+ public void bindNotification(final PackageManager pm,
+ final INotificationManager iNotificationManager,
+ final String pkg,
+ final NotificationChannel notificationChannel,
+ final int numChannels,
+ final StatusBarNotification sbn,
+ final CheckSaveListener checkSaveListener,
+ final OnSettingsClickListener onSettingsClick,
+ final OnAppSettingsClickListener onAppSettingsClick,
+ final Set<String> nonBlockablePkgs,
+ boolean negativeUserSentiment) throws RemoteException {
mINotificationManager = iNotificationManager;
mPkg = pkg;
mNumNotificationChannels = numChannels;
@@ -133,6 +150,7 @@
mOnSettingsClickListener = onSettingsClick;
mSingleNotificationChannel = notificationChannel;
mStartingUserImportance = mChosenImportance = mSingleNotificationChannel.getImportance();
+ mNegativeUserSentiment = negativeUserSentiment;
int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
pkg, mAppUid, false /* includeDeleted */);
@@ -227,27 +245,30 @@
}
private void bindPrompt() {
- final TextView channelName = findViewById(R.id.channel_name);
final TextView blockPrompt = findViewById(R.id.block_prompt);
+ bindName();
if (mNonblockable) {
- if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) {
- channelName.setVisibility(View.GONE);
- } else {
- channelName.setText(mSingleNotificationChannel.getName());
- }
-
blockPrompt.setText(R.string.notification_unblockable_desc);
} else {
- if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) {
- channelName.setVisibility(View.GONE);
+ if (mNegativeUserSentiment) {
+ blockPrompt.setText(R.string.inline_blocking_helper);
+ } else if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) {
blockPrompt.setText(R.string.inline_keep_showing_app);
} else {
- channelName.setText(mSingleNotificationChannel.getName());
blockPrompt.setText(R.string.inline_keep_showing);
}
}
}
+ private void bindName() {
+ final TextView channelName = findViewById(R.id.channel_name);
+ if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) {
+ channelName.setVisibility(View.GONE);
+ } else {
+ channelName.setText(mSingleNotificationChannel.getName());
+ }
+ }
+
private boolean hasImportanceChanged() {
return mSingleNotificationChannel != null && mStartingUserImportance != mChosenImportance;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
index 43be44d..0c19ec0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar;
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+
import android.view.View;
import android.view.ViewGroup;
@@ -179,4 +181,11 @@
* @return true if has pulsing notifications
*/
boolean hasPulsingNotifications();
+
+ /**
+ * Apply parameters of the expand animation to the layout
+ */
+ default void applyExpandAnimationParams(ExpandAnimationParameters params) {}
+
+ default void setExpandingNotification(ExpandableNotificationRow row) {}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 12641a0..5263bb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -16,9 +16,7 @@
package com.android.systemui.statusbar;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.os.Handler;
-import android.service.notification.StatusBarNotification;
import android.view.View;
/**
@@ -48,7 +46,7 @@
* Runs the given intent. The presenter may want to run some animations or close itself when
* this happens.
*/
- void startNotificationGutsIntent(Intent intent, int appUid);
+ void startNotificationGutsIntent(Intent intent, int appUid, ExpandableNotificationRow row);
/**
* Returns the Handler for NotificationPresenter.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 266c09b..cd4c7ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.service.notification.NotificationListenerService;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -338,6 +339,9 @@
stack.push(notificationChildren.get(i));
}
}
+
+ row.showBlockingHelper(entry.userSentiment ==
+ NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
}
mPresenter.onUpdateRowStates();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 97e3d22..cfc69a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -47,7 +47,6 @@
private final Delegate mDelegate;
public RemoteInputController(Delegate delegate) {
- addCallback(Dependency.get(StatusBarWindowManager.class));
mDelegate = delegate;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
new file mode 100644
index 0000000..8336d29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2018 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.notification;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityOptions;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.MathUtils;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.ViewRootImpl;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationListContainer;
+import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.StatusBarWindowView;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+
+import java.util.function.Consumer;
+
+/**
+ * A class that allows activities to be launched in a seamless way where the notification
+ * transforms nicely into the starting window.
+ */
+public class ActivityLaunchAnimator {
+
+ private static final int ANIMATION_DURATION = 400;
+ public static final long ANIMATION_DURATION_FADE_CONTENT = 67;
+ public static final long ANIMATION_DURATION_FADE_APP = 200;
+ public static final long ANIMATION_DELAY_ICON_FADE_IN = ANIMATION_DURATION -
+ CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY
+ - 16;
+ private final NotificationPanelView mNotificationPanel;
+ private final NotificationListContainer mNotificationContainer;
+ private final StatusBarWindowView mStatusBarWindow;
+ private final Consumer<Boolean> mPanelCollapser;
+
+ public ActivityLaunchAnimator(StatusBarWindowView statusBarWindow,
+ Consumer<Boolean> panelCollapser,
+ NotificationPanelView notificationPanel,
+ NotificationListContainer container) {
+ mNotificationPanel = notificationPanel;
+ mNotificationContainer = container;
+ mStatusBarWindow = statusBarWindow;
+ mPanelCollapser = panelCollapser;
+ }
+
+ public ActivityOptions getLaunchAnimation(
+ ExpandableNotificationRow sourceNofitication) {
+ AnimationRunner animationRunner = new AnimationRunner(sourceNofitication);
+ return ActivityOptions.makeRemoteAnimation(
+ new RemoteAnimationAdapter(animationRunner, 1000 /* Duration */, 0 /* delay */));
+ }
+
+ class AnimationRunner extends IRemoteAnimationRunner.Stub {
+
+ private final ExpandableNotificationRow mSourceNotification;
+ private final ExpandAnimationParameters mParams;
+ private final Rect mWindowCrop = new Rect();
+ private boolean mLeashShown;
+ private boolean mInstantCollapsePanel = true;
+
+ public AnimationRunner(ExpandableNotificationRow sourceNofitication) {
+ mSourceNotification = sourceNofitication;
+ mParams = new ExpandAnimationParameters();
+ }
+
+ @Override
+ public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets,
+ IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
+ throws RemoteException {
+ mSourceNotification.post(() -> {
+ boolean first = true;
+ for (RemoteAnimationTarget app : remoteAnimationTargets) {
+ if (app.mode == RemoteAnimationTarget.MODE_OPENING) {
+ setExpandAnimationRunning(true);
+ mInstantCollapsePanel = app.position.y == 0
+ && app.sourceContainerBounds.height()
+ >= mNotificationPanel.getHeight();
+ if (!mInstantCollapsePanel) {
+ mNotificationPanel.collapseWithDuration(ANIMATION_DURATION);
+ }
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ mParams.startPosition = mSourceNotification.getLocationOnScreen();
+ mParams.startTranslationZ = mSourceNotification.getTranslationZ();
+ int targetWidth = app.sourceContainerBounds.width();
+ int notificationHeight = mSourceNotification.getActualHeight();
+ int notificationWidth = mSourceNotification.getWidth();
+ anim.setDuration(ANIMATION_DURATION);
+ anim.setInterpolator(Interpolators.LINEAR);
+ anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mParams.linearProgress = animation.getAnimatedFraction();
+ float progress
+ = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ mParams.linearProgress);
+ int newWidth = (int) MathUtils.lerp(notificationWidth,
+ targetWidth, progress);
+ mParams.left = (int) ((targetWidth - newWidth) / 2.0f);
+ mParams.right = mParams.left + newWidth;
+ mParams.top = (int) MathUtils.lerp(mParams.startPosition[1],
+ app.position.y, progress);
+ mParams.bottom = (int) MathUtils.lerp(mParams.startPosition[1]
+ + notificationHeight,
+ app.position.y + app.sourceContainerBounds.bottom,
+ progress);
+ applyParamsToWindow(app);
+ applyParamsToNotification(mParams);
+ applyParamsToNotificationList(mParams);
+ }
+ });
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setExpandAnimationRunning(false);
+ if (mInstantCollapsePanel) {
+ mPanelCollapser.accept(false /* animate */);
+ }
+ try {
+ iRemoteAnimationFinishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+ anim.start();
+ break;
+ }
+ }
+ });
+ }
+
+ private void setExpandAnimationRunning(boolean running) {
+ mNotificationPanel.setLaunchingNotification(running);
+ mSourceNotification.setExpandAnimationRunning(running);
+ mStatusBarWindow.setExpandAnimationRunning(running);
+ mNotificationContainer.setExpandingNotification(running ? mSourceNotification : null);
+ if (!running) {
+ applyParamsToNotification(null);
+ applyParamsToNotificationList(null);
+ }
+
+ }
+
+ private void applyParamsToNotificationList(ExpandAnimationParameters params) {
+ mNotificationContainer.applyExpandAnimationParams(params);
+ mNotificationPanel.applyExpandAnimationParams(params);
+ }
+
+ private void applyParamsToNotification(ExpandAnimationParameters params) {
+ mSourceNotification.applyExpandAnimationParams(params);
+ }
+
+ private void applyParamsToWindow(RemoteAnimationTarget app) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ if (!mLeashShown) {
+ t.show(app.leash);
+ mLeashShown = true;
+ }
+ Matrix m = new Matrix();
+ m.postTranslate(0, (float) (mParams.top - app.position.y));
+ t.setMatrix(app.leash, m, new float[9]);
+ mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight());
+ t.setWindowCrop(app.leash, mWindowCrop);
+ ViewRootImpl viewRootImpl = mSourceNotification.getViewRootImpl();
+ if (viewRootImpl != null) {
+ Surface systemUiSurface = viewRootImpl.mSurface;
+ t.deferTransactionUntilSurface(app.leash, systemUiSurface,
+ systemUiSurface.getNextFrameNumber());
+ }
+ t.apply();
+ }
+
+ @Override
+ public void onAnimationCancelled() throws RemoteException {
+ }
+ };
+
+ public static class ExpandAnimationParameters {
+ float linearProgress;
+ int[] startPosition;
+ float startTranslationZ;
+ int left;
+ int top;
+ int right;
+ int bottom;
+
+ public ExpandAnimationParameters() {
+ }
+
+ public int getTop() {
+ return top;
+ }
+
+ public int getWidth() {
+ return right - left;
+ }
+
+ public int getHeight() {
+ return bottom - top;
+ }
+
+ public int getTopChange() {
+ return Math.min(top - startPosition[1], 0);
+ }
+
+
+ public float getProgress(long delay, long duration) {
+ return MathUtils.constrain((linearProgress * ANIMATION_DURATION - delay)
+ / duration, 0.0f, 1.0f);
+ }
+
+ public float getStartTranslationZ() {
+ return startTranslationZ;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 61cb61c..f7f791e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -51,6 +51,8 @@
public static final String TAG = "CollapsedStatusBarFragment";
private static final String EXTRA_PANEL_STATE = "panel_state";
+ public static final int FADE_IN_DURATION = 320;
+ public static final int FADE_IN_DELAY = 50;
private PhoneStatusBarView mStatusBar;
private KeyguardMonitor mKeyguardMonitor;
private NetworkController mNetworkController;
@@ -257,9 +259,9 @@
}
v.animate()
.alpha(1f)
- .setDuration(320)
+ .setDuration(FADE_IN_DURATION)
.setInterpolator(Interpolators.ALPHA_IN)
- .setStartDelay(50)
+ .setStartDelay(FADE_IN_DELAY)
// We need to clean up any pending end action from animateHide if we call
// both hide and show in the same frame before the animation actually gets started.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index dc51b1c..61c8027 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -124,6 +124,7 @@
private AccessibilityManager mAccessibilityManager;
private MagnificationContentObserver mMagnificationObserver;
private ContentResolver mContentResolver;
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private int mDisabledFlags1;
private StatusBar mStatusBar;
@@ -224,7 +225,7 @@
mNavigationBarView = (NavigationBarView) view;
mNavigationBarView.setDisabledFlags(mDisabledFlags1);
- mNavigationBarView.setComponents(mRecents, mDivider);
+ mNavigationBarView.setComponents(mRecents, mDivider, mStatusBar.getPanel());
mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
if (savedInstanceState != null) {
@@ -355,6 +356,7 @@
mLastRotationSuggestion = rotation; // Remember rotation for click
setRotateSuggestionButtonState(true);
rescheduleRotationTimeout(false);
+ mMetricsLogger.visible(MetricsEvent.ROTATION_SUGGESTION_SHOWN);
}
}
@@ -612,7 +614,7 @@
if (shouldDisableNavbarGestures()) {
return false;
}
- MetricsLogger.action(getContext(), MetricsEvent.ACTION_ASSIST_LONG_PRESS);
+ mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS);
mAssistManager.startAssist(new Bundle() /* args */);
mStatusBar.awakenDreams();
@@ -768,6 +770,7 @@
}
private void onRotateSuggestionClick(View v) {
+ mMetricsLogger.action(MetricsEvent.ACTION_ROTATION_SUGGESTION_ACCEPTED);
mRotationLockController.setRotationLockedAtAngle(true, mLastRotationSuggestion);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index ff923e5..0954fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -34,6 +34,7 @@
import com.android.systemui.OverviewProxyService;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
+import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.utilities.Utilities;
@@ -78,6 +79,7 @@
private final int mScrollTouchSlop;
private final Matrix mTransformGlobalMatrix = new Matrix();
private final Matrix mTransformLocalMatrix = new Matrix();
+ private final StatusBar mStatusBar;
private int mTouchDownX;
private int mTouchDownY;
private boolean mDownOnRecents;
@@ -90,6 +92,7 @@
public NavigationBarGestureHelper(Context context) {
mContext = context;
+ mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
Resources r = context.getResources();
mScrollTouchSlop = r.getDimensionPixelSize(R.dimen.navigation_bar_min_swipe_distance);
mQuickScrubController = new QuickScrubController(context);
@@ -146,7 +149,8 @@
break;
}
}
- if (!mQuickScrubController.onInterceptTouchEvent(event)) {
+ if (mStatusBar.isPresenterFullyCollapsed()
+ && !mQuickScrubController.onInterceptTouchEvent(event)) {
proxyMotionEvents(event);
return false;
}
@@ -304,7 +308,8 @@
}
public boolean onTouchEvent(MotionEvent event) {
- boolean result = mQuickScrubController.onTouchEvent(event) || proxyMotionEvents(event);
+ boolean result = mStatusBar.isPresenterFullyCollapsed()
+ && (mQuickScrubController.onTouchEvent(event) || proxyMotionEvents(event));
if (mDockWindowEnabled) {
result |= handleDockWindowEvent(event);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 9bef0ee..9f4d35e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -126,6 +126,7 @@
private RecentsComponent mRecentsComponent;
private Divider mDivider;
private SwipeUpOnboarding mSwipeUpOnboarding;
+ private NotificationPanelView mPanelView;
private class NavTransitionListener implements TransitionListener {
private boolean mBackTransitioning;
@@ -206,7 +207,7 @@
}
private final OverviewProxyListener mOverviewProxyListener = isConnected -> {
- setSlippery(!isConnected);
+ updateSlippery();
setDisabledFlags(mDisabledFlags, true);
setUpSwipeUpOnboarding(isConnected);
};
@@ -251,9 +252,11 @@
return mBarTransitions.getLightTransitionsController();
}
- public void setComponents(RecentsComponent recentsComponent, Divider divider) {
+ public void setComponents(RecentsComponent recentsComponent, Divider divider,
+ NotificationPanelView panel) {
mRecentsComponent = recentsComponent;
mDivider = divider;
+ mPanelView = panel;
if (mGestureHelper instanceof NavigationBarGestureHelper) {
((NavigationBarGestureHelper) mGestureHelper).setComponents(
recentsComponent, divider, this);
@@ -571,6 +574,14 @@
}
}
+ public void onPanelExpandedChange(boolean expanded) {
+ updateSlippery();
+ }
+
+ private void updateSlippery() {
+ setSlippery(mOverviewProxyService.getProxy() != null && mPanelView.isFullyExpanded());
+ }
+
private void setSlippery(boolean slippery) {
boolean changed = false;
final ViewGroup navbarView = ((ViewGroup) getParent());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 66cb59e..31b8159 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -45,7 +47,6 @@
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
-
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.keyguard.KeyguardStatusView;
@@ -65,6 +66,7 @@
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
@@ -241,6 +243,8 @@
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private boolean mUserSetupComplete;
private int mQsNotificationTopPadding;
+ private float mExpandOffset;
+ private boolean mHideIconsDuringNotificationLaunch = true;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -670,7 +674,7 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mBlockTouches || mQs.isCustomizing()) {
+ if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
return false;
}
initDownStates(event);
@@ -1675,8 +1679,9 @@
if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
return 0;
}
- float translation = NotificationUtils.interpolate(-mQsMinExpansionHeight, 0,
- mNotificationStackScroller.getAppearFraction(mExpandedHeight));
+ float translation = MathUtils.lerp(-mQsMinExpansionHeight, 0,
+ Math.min(1.0f, mNotificationStackScroller.getAppearFraction(mExpandedHeight)))
+ + mExpandOffset;
return Math.min(0, translation);
}
@@ -2540,6 +2545,9 @@
}
public boolean hideStatusBarIconsWhenExpanded() {
+ if (mLaunchingNotification) {
+ return mHideIconsDuringNotificationLaunch;
+ }
return !isFullWidth() || !mShowIconsWhenExpanded;
}
@@ -2624,6 +2632,7 @@
public void setPulsing(Collection<HeadsUpManager.HeadsUpEntry> pulsing) {
mKeyguardStatusView.setPulsing(pulsing != null);
+ positionClockAndNotifications();
mNotificationStackScroller.setPulsing(pulsing, mKeyguardStatusView.getLocationOnScreen()[1]
+ mKeyguardStatusView.getClockBottom());
}
@@ -2665,4 +2674,19 @@
public LockIcon getLockIcon() {
return mKeyguardBottomArea.getLockIcon();
}
+
+ public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+ mExpandOffset = params != null ? params.getTopChange() : 0;
+ updateQsExpansion();
+ if (params != null) {
+ boolean hideIcons = params.getProgress(
+ ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
+ if (hideIcons != mHideIconsDuringNotificationLaunch) {
+ mHideIconsDuringNotificationLaunch = hideIcons;
+ if (!hideIcons) {
+ mStatusBar.recomputeDisableFlags(true /* animate */);
+ }
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 2fc22ca..a62a424 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -60,12 +60,15 @@
public static final String TAG = PanelView.class.getSimpleName();
private static final int INITIAL_OPENING_PEEK_DURATION = 200;
private static final int PEEK_ANIMATION_DURATION = 360;
+ private static final int NO_FIXED_DURATION = -1;
private long mDownTime;
private float mMinExpandHeight;
private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
private boolean mPanelUpdateWhenAnimatorEnds;
private boolean mVibrateOnOpening;
private boolean mVibrationEnabled;
+ protected boolean mLaunchingNotification;
+ private int mFixedDuration = NO_FIXED_DURATION;
private final void logf(String fmt, Object... args) {
Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
@@ -785,6 +788,9 @@
if (vel == 0) {
animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
}
+ if (mFixedDuration != NO_FIXED_DURATION) {
+ animator.setDuration(mFixedDuration);
+ }
}
animator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@@ -1249,4 +1255,14 @@
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
mHeadsUpManager = headsUpManager;
}
+
+ public void setLaunchingNotification(boolean launchingNotification) {
+ mLaunchingNotification = launchingNotification;
+ }
+
+ public void collapseWithDuration(int animationDuration) {
+ mFixedDuration = animationDuration;
+ collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+ mFixedDuration = NO_FIXED_DURATION;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index b181212..cc5a93c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -250,6 +250,9 @@
super.panelExpansionChanged(frac, expanded);
mPanelFraction = frac;
updateScrimFraction();
+ if ((frac == 0 || frac == 1) && mBar.getNavigationBarView() != null) {
+ mBar.getNavigationBarView().onPanelExpandedChange(expanded);
+ }
}
private void updateScrimFraction() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
index ee1d088..001a1a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
@@ -19,6 +19,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -82,7 +83,10 @@
private boolean mDragPositive;
private boolean mIsVertical;
private boolean mIsRTL;
- private float mMaxTrackPaintAlpha;
+ private float mTrackAlpha;
+ private int mLightTrackColor;
+ private int mDarkTrackColor;
+ private float mDarkIntensity;
private final Handler mHandler = new Handler();
private final Interpolator mQuickScrubEndInterpolator = new DecelerateInterpolator();
@@ -98,9 +102,10 @@
private final ValueAnimator mButtonAnimator;
private final AnimatorSet mQuickScrubEndAnimator;
private final Context mContext;
+ private final ArgbEvaluator mTrackColorEvaluator = new ArgbEvaluator();
private final AnimatorUpdateListener mTrackAnimatorListener = valueAnimator -> {
- mTrackPaint.setAlpha(Math.round((float) valueAnimator.getAnimatedValue() * 255));
+ mTrackAlpha = (float) valueAnimator.getAnimatedValue();
mNavigationBarView.invalidate();
};
@@ -167,6 +172,7 @@
mGestureDetector = new GestureDetector(mContext, mGestureListener);
mTrackThickness = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_thickness);
mTrackPadding = getDimensionPixelSize(mContext, R.dimen.nav_quick_scrub_track_edge_padding);
+ mTrackPaint.setAlpha(0);
mTrackAnimator = ObjectAnimator.ofFloat();
mTrackAnimator.addUpdateListener(mTrackAnimatorListener);
@@ -291,6 +297,10 @@
@Override
public void onDraw(Canvas canvas) {
+ int color = (int) mTrackColorEvaluator.evaluate(mDarkIntensity, mLightTrackColor,
+ mDarkTrackColor);
+ mTrackPaint.setColor(color);
+ mTrackPaint.setAlpha((int) (mTrackPaint.getAlpha() * mTrackAlpha));
canvas.drawRect(mTrackRect, mTrackPaint);
}
@@ -326,13 +336,8 @@
@Override
public void onDarkIntensityChange(float intensity) {
- if (intensity == 0) {
- mTrackPaint.setColor(mContext.getColor(R.color.quick_step_track_background_light));
- } else if (intensity == 1) {
- mTrackPaint.setColor(mContext.getColor(R.color.quick_step_track_background_dark));
- }
- mMaxTrackPaintAlpha = mTrackPaint.getAlpha() * 1f / 255;
- mTrackPaint.setAlpha(0);
+ mDarkIntensity = intensity;
+ mNavigationBarView.invalidate();
}
@Override
@@ -365,7 +370,9 @@
private void startQuickScrub() {
if (!mQuickScrubActive) {
mQuickScrubActive = true;
- mTrackAnimator.setFloatValues(0, mMaxTrackPaintAlpha);
+ mLightTrackColor = mContext.getColor(R.color.quick_step_track_background_light);
+ mDarkTrackColor = mContext.getColor(R.color.quick_step_track_background_dark);
+ mTrackAnimator.setFloatValues(0, 1);
mTrackAnimator.start();
try {
mOverviewEventSender.getProxy().onQuickScrubStart();
@@ -382,7 +389,7 @@
mHandler.removeCallbacks(mLongPressRunnable);
if (mDraggingActive || mQuickScrubActive) {
mButtonAnimator.setIntValues((int) mTranslation, 0);
- mTrackAnimator.setFloatValues(mTrackPaint.getAlpha() * 1f / 255, 0);
+ mTrackAnimator.setFloatValues(mTrackAlpha, 0);
mQuickScrubEndAnimator.start();
try {
mOverviewEventSender.getProxy().onQuickScrubEnd();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index d13ecae..7d84550 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -100,6 +100,8 @@
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
import android.service.vr.IVrStateCallbacks;
+import android.text.SpannedString;
+import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
@@ -205,6 +207,7 @@
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -586,6 +589,7 @@
private NavigationBarFragment mNavigationBar;
private View mNavigationBarView;
+ private ActivityLaunchAnimator mActivityLaunchAnimator;
@Override
public void start() {
@@ -755,6 +759,10 @@
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
+ mActivityLaunchAnimator = new ActivityLaunchAnimator(mStatusBarWindow,
+ this::collapsePanel,
+ mNotificationPanel,
+ mStackScroller);
mGutsManager.setUpWithPresenter(this, mEntryManager, mStackScroller, mCheckSaveListener,
key -> {
try {
@@ -2760,6 +2768,7 @@
mStackScroller.requestDisallowDismiss();
}
});
+ mRemoteInputManager.getController().addCallback(mStatusBarWindowManager);
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
@@ -2795,7 +2804,8 @@
intent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
int result = ActivityManager.START_CANCELED;
- ActivityOptions options = new ActivityOptions(getActivityOptions());
+ ActivityOptions options = new ActivityOptions(getActivityOptions(
+ null /* sourceNotification */));
options.setDisallowEnterPictureInPictureWhileLaunching(
disallowEnterPictureInPictureWhileLaunching);
if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
@@ -4910,6 +4920,7 @@
ActivityManager.getService().resumeAppSwitches();
} catch (RemoteException e) {
}
+ int launchResult = ActivityManager.START_CANCELED;
if (intent != null) {
// If we are launching a work activity and require to launch
// separate work challenge, we defer the activity action and cancel
@@ -4924,12 +4935,30 @@
notificationKey)) {
// Show work challenge, do not run PendingIntent and
// remove notification
+ collapsePanel();
return;
}
}
}
+ Intent fillInIntent = null;
+ Entry entry = row.getEntry();
+ CharSequence remoteInputText = null;
+ RemoteInputController controller = mRemoteInputManager.getController();
+ if (controller.isRemoteInputActive(entry)) {
+ remoteInputText = row.getActiveRemoteInputText();
+ }
+ if (TextUtils.isEmpty(remoteInputText)
+ && !TextUtils.isEmpty(entry.remoteInputText)) {
+ remoteInputText = entry.remoteInputText;
+ }
+ if (!TextUtils.isEmpty(remoteInputText)
+ && !controller.isSpinning(entry.key)) {
+ fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT,
+ remoteInputText.toString());
+ }
try {
- intent.send(null, 0, null, null, null, null, getActivityOptions());
+ launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
+ null, null, getActivityOptions(row));
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
@@ -4941,6 +4970,13 @@
mAssistManager.hideAssist();
}
}
+ if (shouldCollapse(launchResult)) {
+ if (Looper.getMainLooper().isCurrentThread()) {
+ collapsePanel();
+ } else {
+ mStackScroller.post(this::collapsePanel);
+ }
+ }
try {
mBarService.onNotificationClick(notificationKey);
@@ -4963,19 +4999,45 @@
new Thread(runnable).start();
}
- if (!mNotificationPanel.isFullyCollapsed()) {
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
- true /* delayed */);
- visibilityChanged(false);
-
- return true;
- } else {
- return false;
- }
+ return !mNotificationPanel.isFullyCollapsed();
}, afterKeyguardGone);
}
+ private boolean shouldCollapse(int launchResult) {
+ return mState != StatusBarState.SHADE
+ || (launchResult != ActivityManager.START_TASK_TO_FRONT
+ && launchResult != ActivityManager.START_SUCCESS);
+ }
+
+ public void onExpandAnimationFinished() {
+ if (!isPresenterFullyCollapsed()) {
+ instantCollapseNotificationPanel();
+ visibilityChanged(false);
+ }
+ }
+
+ public void collapsePanel(boolean animate) {
+ if (animate) {
+ collapsePanel();
+ } else if (!isPresenterFullyCollapsed()) {
+ instantCollapseNotificationPanel();
+ visibilityChanged(false);
+ }
+ }
+
+ private boolean collapsePanel() {
+ if (!mNotificationPanel.isFullyCollapsed()) {
+ // close the shade if it was open
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+ true /* delayed */);
+ visibilityChanged(false);
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
private void removeNotification(StatusBarNotification notification) {
// We have to post it to the UI thread for synchronization
mHandler.post(() -> {
@@ -5058,15 +5120,20 @@
}
@Override
- public void startNotificationGutsIntent(final Intent intent, final int appUid) {
+ public void startNotificationGutsIntent(final Intent intent, final int appUid,
+ ExpandableNotificationRow row) {
dismissKeyguardThenExecute(() -> {
AsyncTask.execute(() -> {
- TaskStackBuilder.create(mContext)
+ int launchResult = TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
- .startActivities(getActivityOptions(),
+ .startActivities(getActivityOptions(row),
new UserHandle(UserHandle.getUserId(appUid)));
+ if (shouldCollapse(launchResult)) {
+ // Putting it back on the main thread, since we're touching views
+ mStatusBarWindow.post(() -> animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
+ }
});
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
return true;
}, false /* afterKeyguardGone */);
}
@@ -5192,7 +5259,8 @@
} catch (RemoteException e) {
}
try {
- intent.send(null, 0, null, null, null, null, getActivityOptions());
+ intent.send(null, 0, null, null, null, null, getActivityOptions(
+ null /* sourceNotification */));
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
@@ -5205,16 +5273,7 @@
}
}).start();
- if (!mNotificationPanel.isFullyCollapsed()) {
- // close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
- true /* delayed */);
- visibilityChanged(false);
-
- return true;
- } else {
- return false;
- }
+ return collapsePanel();
}, afterKeyguardGone);
}
@@ -5229,10 +5288,15 @@
return true;
}
- protected Bundle getActivityOptions() {
+ protected Bundle getActivityOptions(ExpandableNotificationRow sourceNotification) {
+ ActivityOptions options;
+ if (sourceNotification != null) {
+ options = mActivityLaunchAnimator.getLaunchAnimation(sourceNotification);
+ } else {
+ options = ActivityOptions.makeBasic();
+ }
// Anything launched from the notification shade should always go into the secondary
// split-screen windowing mode.
- final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
return options.toBundle();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 4accd86..f7d0967 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -88,6 +88,7 @@
private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
private boolean mTouchCancelled;
private boolean mTouchActive;
+ private boolean mExpandAnimationRunning;
public StatusBarWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -267,7 +268,7 @@
|| ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
setTouchActive(false);
}
- if (mTouchCancelled) {
+ if (mTouchCancelled || mExpandAnimationRunning) {
return false;
}
mFalsingManager.onTouchEvent(ev, getWidth(), getHeight());
@@ -388,6 +389,10 @@
}
}
+ public void setExpandAnimationRunning(boolean expandAnimationRunning) {
+ mExpandAnimationRunning = expandAnimationRunning;
+ }
+
public class LayoutParams extends FrameLayout.LayoutParams {
public boolean ignoreRightInset;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 0b666a6..1e894ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -32,6 +32,7 @@
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.support.annotation.VisibleForTesting;
import com.android.systemui.util.Utils;
import java.util.ArrayList;
@@ -105,7 +106,8 @@
}
// When enabling location, a user consent dialog will pop up, and the
// setting won't be fully enabled until the user accepts the agreement.
- updateLocationEnabled(mContext, enabled, currentUserId);
+ updateLocationEnabled(mContext, enabled, currentUserId,
+ Settings.Secure.LOCATION_CHANGER_QUICK_SETTINGS);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index b63c1da..179c0d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -169,6 +169,10 @@
}
}
+ public CharSequence getText() {
+ return mEditText.getText();
+ }
+
public static RemoteInputView inflate(Context context, ViewGroup root,
NotificationData.Entry entry,
RemoteInputController controller) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index ebf4cda..424858a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -68,6 +68,8 @@
private boolean mUnlockHintRunning;
private boolean mQsCustomizerShowing;
private int mIntrinsicPadding;
+ private int mExpandAnimationTopChange;
+ private ExpandableNotificationRow mExpandingNotification;
public AmbientState(Context context) {
reload(context);
@@ -77,9 +79,25 @@
* Reload the dimens e.g. if the density changed.
*/
public void reload(Context context) {
- mZDistanceBetweenElements = Math.max(1, context.getResources()
+ mZDistanceBetweenElements = getZDistanceBetweenElements(context);
+ mBaseZHeight = getBaseHeight(mZDistanceBetweenElements);
+ }
+
+ private static int getZDistanceBetweenElements(Context context) {
+ return Math.max(1, context.getResources()
.getDimensionPixelSize(R.dimen.z_distance_between_notifications));
- mBaseZHeight = 4 * mZDistanceBetweenElements;
+ }
+
+ private static int getBaseHeight(int zdistanceBetweenElements) {
+ return 4 * zdistanceBetweenElements;
+ }
+
+ /**
+ * @return the launch height for notifications that are launched
+ */
+ public static int getNotificationLaunchHeight(Context context) {
+ int zDistance = getZDistanceBetweenElements(context);
+ return getBaseHeight(zDistance) * 2;
}
/**
@@ -202,7 +220,8 @@
}
public int getInnerHeight() {
- return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding, mLayoutMinHeight);
+ return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding
+ - mExpandAnimationTopChange, mLayoutMinHeight);
}
public boolean isShadeExpanded() {
@@ -380,4 +399,20 @@
public boolean isDozingAndNotPulsing(ExpandableNotificationRow row) {
return isDark() && !isPulsing(row.getEntry());
}
+
+ public void setExpandAnimationTopChange(int expandAnimationTopChange) {
+ mExpandAnimationTopChange = expandAnimationTopChange;
+ }
+
+ public void setExpandingNotification(ExpandableNotificationRow row) {
+ mExpandingNotification = row;
+ }
+
+ public ExpandableNotificationRow getExpandingNotification() {
+ return mExpandingNotification;
+ }
+
+ public int getExpandAnimationTopChange() {
+ return mExpandAnimationTopChange;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
index 0650e23..3bf7d89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
@@ -467,4 +467,21 @@
return getChildTag(view, TAG_END_HEIGHT);
}
}
+
+ @Override
+ public void cancelAnimations(View view) {
+ super.cancelAnimations(view);
+ Animator animator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
+ if (animator != null) {
+ animator.cancel();
+ }
+ animator = getChildTag(view, TAG_ANIMATOR_SHADOW_ALPHA);
+ if (animator != null) {
+ animator.cancel();
+ }
+ animator = getChildTag(view, TAG_ANIMATOR_TOP_INSET);
+ if (animator != null) {
+ animator.cancel();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index af3d64b..ad8a0eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.stack;
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -728,7 +730,7 @@
}
private void updateClippingToTopRoundedCorner() {
- Float clipStart = (float) mTopPadding;
+ Float clipStart = (float) mTopPadding + mAmbientState.getExpandAnimationTopChange();
Float clipEnd = clipStart + mCornerRadius;
boolean first = true;
for (int i = 0; i < getChildCount(); i++) {
@@ -1019,7 +1021,9 @@
mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
}
}
- performDismiss(v, mGroupManager, false /* fromAccessibility */);
+ if (v instanceof ExpandableNotificationRow) {
+ ((ExpandableNotificationRow) v).performDismiss(false /* fromAccessibility */);
+ }
mFalsingManager.onNotificationDismissed();
if (mFalsingManager.shouldEnforceBouncer()) {
@@ -1028,26 +1032,6 @@
}
}
- public static void performDismiss(View v, NotificationGroupManager groupManager,
- boolean fromAccessibility) {
- if (!(v instanceof ExpandableNotificationRow)) {
- return;
- }
- ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- if (groupManager.isOnlyChildInGroup(row.getStatusBarNotification())) {
- ExpandableNotificationRow groupSummary =
- groupManager.getLogicalGroupSummary(row.getStatusBarNotification());
- if (groupSummary.isClearable()) {
- performDismiss(groupSummary, groupManager, fromAccessibility);
- }
- }
- row.setDismissed(true, fromAccessibility);
- if (row.isClearable()) {
- row.performDismiss();
- }
- if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
- }
-
@Override
public void onChildSnappedBack(View animView, float targetLeft) {
mAmbientState.onDragFinished(animView);
@@ -3006,6 +2990,17 @@
&& (mIsExpanded || isPinnedHeadsUp(child)), child);
}
+ @Override
+ public void setExpandingNotification(ExpandableNotificationRow row) {
+ mAmbientState.setExpandingNotification(row);
+ requestChildrenUpdate();
+ }
+
+ @Override
+ public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+ mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange());
+ requestChildrenUpdate();
+ }
private void updateAnimationState(boolean running, View child) {
if (child instanceof ExpandableNotificationRow) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 2ce6df2..d68a7b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -130,7 +130,8 @@
private void updateClipping(StackScrollState resultState,
StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
float drawStart = !ambientState.isOnKeyguard() ? ambientState.getTopPadding()
- + ambientState.getStackTranslation() : 0;
+ + ambientState.getStackTranslation() + ambientState.getExpandAnimationTopChange()
+ : 0;
float previousNotificationEnd = 0;
float previousNotificationStart = 0;
int childCount = algorithmState.visibleChildren.size();
@@ -320,6 +321,10 @@
lastView = v;
}
}
+ ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification();
+ state.indexOfExpandingNotification = expandingNotification != null
+ ? state.visibleChildren.indexOf(expandingNotification)
+ : -1;
}
private float getPaddingForValue(Float increasedPadding) {
@@ -381,6 +386,9 @@
childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
float inset = ambientState.getTopPadding() + ambientState.getStackTranslation();
+ if (i < algorithmState.getIndexOfExpandingNotification()) {
+ inset += ambientState.getExpandAnimationTopChange();
+ }
if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) {
// Even if we're not scrolled away we're in view and we're also not in the
// shelf. We can relax the constraints and let us scroll off the top!
@@ -394,7 +402,7 @@
childViewState.yTranslation = ambientState.getInnerHeight() - childHeight
+ ambientState.getStackTranslation() * 0.25f;
} else {
- clampPositionToShelf(childViewState, ambientState);
+ clampPositionToShelf(child, childViewState, ambientState);
}
currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
@@ -492,10 +500,12 @@
* Clamp the height of the child down such that its end is at most on the beginning of
* the shelf.
*
+ * @param child
* @param childViewState the view state of the child
* @param ambientState the ambient state
*/
- private void clampPositionToShelf(ExpandableViewState childViewState,
+ private void clampPositionToShelf(ExpandableView child,
+ ExpandableViewState childViewState,
AmbientState ambientState) {
if (ambientState.getShelf() == null) {
return;
@@ -505,7 +515,7 @@
- ambientState.getShelf().getIntrinsicHeight();
childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
if (childViewState.yTranslation >= shelfStart) {
- childViewState.hidden = true;
+ childViewState.hidden = !child.isExpandAnimationRunning();
childViewState.inShelf = true;
childViewState.headsUpIsVisible = false;
}
@@ -602,6 +612,7 @@
* The padding after each child measured in pixels.
*/
public final HashMap<ExpandableView, Float> paddingMap = new HashMap<>();
+ private int indexOfExpandingNotification;
public int getPaddingAfterChild(ExpandableView child) {
Float padding = paddingMap.get(child);
@@ -611,6 +622,10 @@
}
return (int) padding.floatValue();
}
+
+ public int getIndexOfExpandingNotification() {
+ return indexOfExpandingNotification;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index e3c8503..5c888ac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -514,5 +514,8 @@
@Override
public void onAccessibilityModeChanged(Boolean showA11yStream) {}
+
+ @Override
+ public void onConnectedDeviceChanged(String deviceName) {}
};
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 4464f75..2e23920 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -26,6 +26,8 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.ContentObserver;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IVolumeController;
@@ -105,6 +107,8 @@
private boolean mShowA11yStream;
private boolean mShowVolumeDialog;
private boolean mShowSafetyWarning;
+ private DeviceCallback mDeviceCallback = new DeviceCallback();
+ private AudioDeviceInfo mConnectedDevice;
private boolean mDestroyed;
private VolumePolicy mVolumePolicy;
@@ -180,6 +184,7 @@
} catch (SecurityException e) {
Log.w(TAG, "No access to media sessions", e);
}
+ mAudio.registerAudioDeviceCallback(mDeviceCallback, mWorker);
}
public void setVolumePolicy(VolumePolicy policy) {
@@ -205,6 +210,7 @@
mMediaSessions.destroy();
mObserver.destroy();
mReceiver.destroy();
+ mAudio.unregisterAudioDeviceCallback(mDeviceCallback);
mWorkerThread.quitSafely();
}
@@ -664,6 +670,7 @@
case USER_ACTIVITY: onUserActivityW(); break;
case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
+
}
}
}
@@ -803,6 +810,18 @@
});
}
}
+
+ @Override
+ public void onConnectedDeviceChanged(String deviceName) {
+ for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+ entry.getValue().post(new Runnable() {
+ @Override
+ public void run() {
+ entry.getKey().onConnectedDeviceChanged(deviceName);
+ }
+ });
+ }
+ }
}
@@ -1005,6 +1024,34 @@
}
}
+ protected final class DeviceCallback extends AudioDeviceCallback {
+ public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+ for (AudioDeviceInfo info : addedDevices) {
+ if (info.isSink()
+ && (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
+ || info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO)) {
+ mConnectedDevice = info;
+ mCallbacks.onConnectedDeviceChanged(info.getProductName().toString());
+ }
+ }
+ }
+
+ public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+ if (mConnectedDevice == null) {
+ mCallbacks.onConnectedDeviceChanged(null);
+ return;
+ }
+ for (AudioDeviceInfo info : removedDevices) {
+ if (info.isSink() == mConnectedDevice.isSink()
+ && Objects.equals(info.getProductName(), mConnectedDevice.getProductName())
+ && info.getType() == mConnectedDevice.getType()) {
+ mConnectedDevice = null;
+ mCallbacks.onConnectedDeviceChanged(null);
+ }
+ }
+ }
+ }
+
public interface UserActivityListener {
void onUserActivity();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 385438c..56b7201 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -48,6 +48,7 @@
import android.provider.Settings;
import android.provider.Settings.Global;
import android.support.v7.media.MediaRouter;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -73,6 +74,7 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
@@ -332,8 +334,11 @@
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.anim = null;
- ImageButton outputChooser = row.view.findViewById(R.id.output_chooser);
- outputChooser.setOnClickListener(mClickOutputChooser);
+ row.outputChooser = row.view.findViewById(R.id.output_chooser);
+ row.outputChooser.setOnClickListener(mClickOutputChooser);
+ row.outputChooser.findViewById(R.id.output_chooser_button)
+ .setOnClickListener(mClickOutputChooser);
+ row.connectedDevice = row.view.findViewById(R.id.volume_row_connected_device);
// forward events above the slider into the slider
row.view.setOnTouchListener(new OnTouchListener() {
@@ -422,7 +427,7 @@
Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
- mContext.startActivity(intent);
+ Dependency.get(ActivityStarter.class).startActivity(intent, true /* dismissShade */);
return true;
});
updateRingerH();
@@ -546,6 +551,13 @@
}
}
+ protected void updateConnectedDeviceH(String deviceName) {
+ for (final VolumeRow row : mRows) {
+ row.connectedDevice.setText(deviceName);
+ Util.setVisOrGone(row.connectedDevice, !TextUtils.isEmpty(deviceName));
+ }
+ }
+
protected void updateRingerH() {
if (mState != null) {
final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
@@ -957,6 +969,11 @@
}
}
+
+ @Override
+ public void onConnectedDeviceChanged(String deviceName) {
+ updateConnectedDeviceH(deviceName);
+ }
};
private final class H extends Handler {
@@ -1155,5 +1172,7 @@
private ObjectAnimator anim; // slider progress animation for non-touch-related updates
private int animTargetProgress;
private int lastAudibleLevel = 1;
+ private View outputChooser;
+ private TextView connectedDevice;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
index 53a7994..5c83d99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -18,9 +18,11 @@
import android.content.Context;
import android.util.ArrayMap;
+import android.util.ArraySet;
public class TestableDependency extends Dependency {
private final ArrayMap<Object, Object> mObjs = new ArrayMap<>();
+ private final ArraySet<Object> mInstantiatedObjects = new ArraySet<>();
public TestableDependency(Context context) {
mContext = context;
@@ -47,6 +49,11 @@
@Override
protected <T> T createDependency(Object key) {
if (mObjs.containsKey(key)) return (T) mObjs.get(key);
+ mInstantiatedObjects.add(key);
return super.createDependency(key);
}
+
+ public <T> boolean hasInstantiatedDependency(Class<T> key) {
+ return mObjs.containsKey(key) || mInstantiatedObjects.contains(key);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index fdb7f8d..0a51e5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -40,6 +40,7 @@
import com.android.systemui.power.PowerUI.WarningsUI;
import com.android.systemui.statusbar.phone.StatusBar;
+import java.time.Duration;
import java.util.concurrent.TimeUnit;
import org.junit.Before;
import org.junit.Test;
@@ -53,18 +54,19 @@
private static final boolean UNPLUGGED = false;
private static final boolean POWER_SAVER_OFF = false;
private static final int ABOVE_WARNING_BUCKET = 1;
+ private static final long ONE_HOUR_MILLIS = Duration.ofHours(1).toMillis();
public static final int BELOW_WARNING_BUCKET = -1;
public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2);
public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4);
private HardwarePropertiesManager mHardProps;
private WarningsUI mMockWarnings;
private PowerUI mPowerUI;
- private EnhancedEstimates mEnhacedEstimates;
+ private EnhancedEstimates mEnhancedEstimates;
@Before
public void setup() {
mMockWarnings = mDependency.injectMockDependency(WarningsUI.class);
- mEnhacedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class);
+ mEnhancedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class);
mHardProps = mock(HardwarePropertiesManager.class);
mContext.putComponent(StatusBar.class, mock(StatusBar.class));
@@ -142,8 +144,44 @@
}
@Test
+ public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsNoShow() {
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold())
+ .thenReturn(Duration.ofHours(1).toMillis());
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+ mPowerUI.start();
+
+ // unplugged device that would not show the non-hybrid notification but would show the
+ // hybrid but the threshold has been overriden to be too low
+ boolean shouldShow =
+ mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+ ABOVE_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD,
+ POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+ assertFalse(shouldShow);
+ }
+
+ @Test
+ public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsShow() {
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold())
+ .thenReturn(Duration.ofHours(5).toMillis());
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+ mPowerUI.start();
+
+ // unplugged device that would not show the non-hybrid notification but would show the
+ // hybrid since the threshold has been overriden to be much higher
+ boolean shouldShow =
+ mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+ ABOVE_WARNING_BUCKET, Long.MAX_VALUE, ABOVE_HYBRID_THRESHOLD,
+ POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+ assertTrue(shouldShow);
+ }
+
+ @Test
public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() {
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
mPowerUI.start();
// unplugged device that would not show the non-hybrid notification but would show the
@@ -157,7 +195,9 @@
@Test
public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() {
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
mPowerUI.start();
// unplugged device that would show the non-hybrid notification and the hybrid
@@ -170,7 +210,9 @@
@Test
public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() {
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
mPowerUI.start();
// unplugged device that would show the non-hybrid but not the hybrid
@@ -183,7 +225,9 @@
@Test
public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() {
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
mPowerUI.start();
// unplugged device that would show the neither due to battery level being good
@@ -196,7 +240,9 @@
@Test
public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() {
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
mPowerUI.start();
// plugged device that would show the neither due to being plugged
@@ -209,7 +255,9 @@
@Test
public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnkown_returnsNoShow() {
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
mPowerUI.start();
// Unknown battery status device that would show the neither due
@@ -222,7 +270,9 @@
@Test
public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() {
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
mPowerUI.start();
// BatterySaverEnabled device that would show the neither due to battery saver
@@ -236,7 +286,10 @@
@Test
public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabled() {
mPowerUI.start();
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+
// device that gets power saver turned on should dismiss
boolean shouldDismiss =
mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
@@ -247,7 +300,9 @@
@Test
public void testShouldDismissLowBatteryWarning_dismissWhenPlugged() {
mPowerUI.start();
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
// device that gets plugged in should dismiss
boolean shouldDismiss =
@@ -259,7 +314,10 @@
@Test
public void testShouldDismissLowBatteryWarning_dismissHybridSignal_showStandardSignal_shouldShow() {
mPowerUI.start();
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+
// would dismiss hybrid but not non-hybrid should not dismiss
boolean shouldDismiss =
mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
@@ -270,7 +328,9 @@
@Test
public void testShouldDismissLowBatteryWarning_showHybridSignal_dismissStandardSignal_shouldShow() {
mPowerUI.start();
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
// would dismiss non-hybrid but not hybrid should not dismiss
boolean shouldDismiss =
@@ -282,7 +342,9 @@
@Test
public void testShouldDismissLowBatteryWarning_showBothSignal_shouldShow() {
mPowerUI.start();
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
// should not dismiss when both would not dismiss
boolean shouldDismiss =
@@ -294,7 +356,9 @@
@Test
public void testShouldDismissLowBatteryWarning_dismissBothSignal_shouldDismiss() {
mPowerUI.start();
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
//should dismiss if both would dismiss
boolean shouldDismiss =
@@ -306,7 +370,9 @@
@Test
public void testShouldDismissLowBatteryWarning_dismissStandardSignal_hybridDisabled_shouldDismiss() {
mPowerUI.start();
- when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(false);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(false);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
// would dismiss non-hybrid with hybrid disabled should dismiss
boolean shouldDismiss =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
new file mode 100644
index 0000000..5f763a4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Verifies that particular sets of dependencies don't have dependencies on others. For example,
+ * code managing notifications shouldn't directly depend on StatusBar, since there are platforms
+ * which want to manage notifications, but don't use StatusBar.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NonPhoneDependencyTest extends SysuiTestCase {
+ @Mock private NotificationPresenter mPresenter;
+ @Mock private NotificationListContainer mListContainer;
+ @Mock private NotificationEntryManager.Callback mEntryManagerCallback;
+ @Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private RemoteInputController.Delegate mDelegate;
+ @Mock private NotificationInfo.CheckSaveListener mCheckSaveListener;
+ @Mock private NotificationGutsManager.OnSettingsClickListener mOnClickListener;
+ @Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
+
+ private Handler mHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mHandler = new Handler(Looper.getMainLooper());
+ when(mPresenter.getHandler()).thenReturn(mHandler);
+ }
+
+ @Test
+ public void testNotificationManagementCodeHasNoDependencyOnStatusBarWindowManager() {
+ NotificationEntryManager entryManager = Dependency.get(NotificationEntryManager.class);
+ NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
+ NotificationListener notificationListener = Dependency.get(NotificationListener.class);
+ NotificationLogger notificationLogger = Dependency.get(NotificationLogger.class);
+ NotificationMediaManager mediaManager = Dependency.get(NotificationMediaManager.class);
+ NotificationRemoteInputManager remoteInputManager =
+ Dependency.get(NotificationRemoteInputManager.class);
+ NotificationLockscreenUserManager lockscreenUserManager =
+ Dependency.get(NotificationLockscreenUserManager.class);
+ NotificationViewHierarchyManager viewHierarchyManager =
+ Dependency.get(NotificationViewHierarchyManager.class);
+
+ when(mPresenter.getNotificationLockscreenUserManager()).thenReturn(lockscreenUserManager);
+ when(mPresenter.getGroupManager()).thenReturn(
+ Dependency.get(NotificationGroupManager.class));
+
+ entryManager.setUpWithPresenter(mPresenter, mListContainer, mEntryManagerCallback,
+ mHeadsUpManager);
+ gutsManager.setUpWithPresenter(mPresenter, entryManager, mListContainer,
+ mCheckSaveListener, mOnClickListener);
+ notificationListener.setUpWithPresenter(mPresenter, entryManager);
+ notificationLogger.setUpWithEntryManager(entryManager, mListContainer);
+ mediaManager.setUpWithPresenter(mPresenter, entryManager);
+ remoteInputManager.setUpWithPresenter(mPresenter, entryManager, mRemoteInputManagerCallback,
+ mDelegate);
+ lockscreenUserManager.setUpWithPresenter(mPresenter, entryManager);
+ viewHierarchyManager.setUpWithPresenter(mPresenter, entryManager, mListContainer);
+
+ assertFalse(mDependency.hasInstantiatedDependency(StatusBarWindowManager.class));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index d77bf69..8e8b3e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -281,6 +281,16 @@
}
@Test
+ public void testbindNotification_BlockingHelper() throws Exception {
+ mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+ TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null,
+ null, null, true);
+ final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
+ assertEquals(View.VISIBLE, view.getVisibility());
+ assertEquals(mContext.getString(R.string.inline_blocking_helper), view.getText());
+ }
+
+ @Test
public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception {
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null,
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index bfec88c..03dfd46 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4872,17 +4872,17 @@
// An autofill service asked to disable autofill for a given application.
// Package: Package of app that is being disabled for autofill
- // Counter: duration (in ms) that autofill will be disabled
// OS: P
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+ // Tag FIELD_AUTOFILL_DURATION: duration (in ms) that autofill will be disabled
AUTOFILL_SERVICE_DISABLED_APP = 1231;
// An autofill service asked to disable autofill for a given activity.
// Package: Package of app whose activity is being disabled for autofill
- // Counter: duration (in ms) that autofill will be disabled
// OS: P
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Tag FIELD_CLASS_NAME: Class name of the activity that is being disabled for autofill
+ // Tag FIELD_AUTOFILL_DURATION: duration (in ms) that autofill will be disabled
AUTOFILL_SERVICE_DISABLED_ACTIVITY = 1232;
// ACTION: Stop an app and turn on background check
@@ -5158,6 +5158,16 @@
// OS: P
NOTIFICATION_ZEN_MODE_ENABLE_DIALOG = 1286;
+ // ACTION: Rotate suggestion accepted in rotation locked mode
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: P
+ ACTION_ROTATION_SUGGESTION_ACCEPTED = 1287;
+
+ // OPEN: Rotation suggestion shown in rotation locked mode
+ // CATEGORY: GLOBAL_SYSTEM_UI
+ // OS: P
+ ROTATION_SUGGESTION_SHOWN = 1288;
+
// ---- End P Constants, all P constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 7c6019e..db70184 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -196,6 +196,10 @@
// Inform the user that unexpectedly rapid network usage is happening
NOTE_NET_RAPID = 45;
+ // Notify the user that carrier Wi-Fi networks are available.
+ // Package: android
+ NOTE_CARRIER_NETWORK_AVAILABLE = 46;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index b32be73..52d0e08e 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -24,8 +24,9 @@
#include <utils/misc.h>
#include <inttypes.h>
+#include <android-base/macros.h>
#include <androidfw/Asset.h>
-#include <androidfw/AssetManager.h>
+#include <androidfw/AssetManager2.h>
#include <androidfw/ResourceTypes.h>
#include <android-base/macros.h>
@@ -1664,18 +1665,22 @@
static jlong
nFileA3DCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path)
{
- AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr);
+ Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr);
if (mgr == nullptr) {
return 0;
}
AutoJavaStringToUTF8 str(_env, _path);
- Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
- if (asset == nullptr) {
- return 0;
+ std::unique_ptr<Asset> asset;
+ {
+ ScopedLock<AssetManager2> locked_mgr(*mgr);
+ asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER);
+ if (asset == nullptr) {
+ return 0;
+ }
}
- jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset);
+ jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset.release());
return id;
}
@@ -1752,22 +1757,25 @@
nFontCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path,
jfloat fontSize, jint dpi)
{
- AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr);
+ Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr);
if (mgr == nullptr) {
return 0;
}
AutoJavaStringToUTF8 str(_env, _path);
- Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
- if (asset == nullptr) {
- return 0;
+ std::unique_ptr<Asset> asset;
+ {
+ ScopedLock<AssetManager2> locked_mgr(*mgr);
+ asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER);
+ if (asset == nullptr) {
+ return 0;
+ }
}
jlong id = (jlong)(uintptr_t)rsFontCreateFromMemory((RsContext)con,
str.c_str(), str.length(),
fontSize, dpi,
asset->getBuffer(false), asset->getLength());
- delete asset;
return id;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
index 0bc95f4..52ab85c 100644
--- a/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/MagnificationGestureHandler.java
@@ -54,6 +54,9 @@
import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayDeque;
+import java.util.Queue;
+
/**
* This class handles magnification in response to touch events.
*
@@ -110,6 +113,7 @@
private static final boolean DEBUG_STATE_TRANSITIONS = false || DEBUG_ALL;
private static final boolean DEBUG_DETECTING = false || DEBUG_ALL;
private static final boolean DEBUG_PANNING_SCALING = false || DEBUG_ALL;
+ private static final boolean DEBUG_EVENT_STREAM = false || DEBUG_ALL;
private static final float MIN_SCALE = 2.0f;
private static final float MAX_SCALE = 5.0f;
@@ -141,6 +145,9 @@
private PointerCoords[] mTempPointerCoords;
private PointerProperties[] mTempPointerProperties;
+ private final Queue<MotionEvent> mDebugInputEventHistory;
+ private final Queue<MotionEvent> mDebugOutputEventHistory;
+
/**
* @param context Context for resolving various magnification-related resources
* @param magnificationController the {@link MagnificationController}
@@ -179,11 +186,28 @@
mScreenStateReceiver = null;
}
+ mDebugInputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null;
+ mDebugOutputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null;
+
transitionTo(mDetectingState);
}
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ if (DEBUG_EVENT_STREAM) {
+ storeEventInto(mDebugInputEventHistory, event);
+ try {
+ onMotionEventInternal(event, rawEvent, policyFlags);
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Exception following input events: " + mDebugInputEventHistory, e);
+ }
+ } else {
+ onMotionEventInternal(event, rawEvent, policyFlags);
+ }
+ }
+
+ private void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (DEBUG_ALL) Slog.i(LOG_TAG, "onMotionEvent(" + event + ")");
if ((!mDetectTripleTap && !mDetectShortcutTrigger)
@@ -273,7 +297,27 @@
coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(),
event.getFlags());
}
- super.onMotionEvent(event, rawEvent, policyFlags);
+ if (DEBUG_EVENT_STREAM) {
+ storeEventInto(mDebugOutputEventHistory, event);
+ try {
+ super.onMotionEvent(event, rawEvent, policyFlags);
+ } catch (Exception e) {
+ throw new RuntimeException(
+ "Exception downstream following input events: " + mDebugInputEventHistory
+ + "\nTransformed into output events: " + mDebugOutputEventHistory,
+ e);
+ }
+ } else {
+ super.onMotionEvent(event, rawEvent, policyFlags);
+ }
+ }
+
+ private static void storeEventInto(Queue<MotionEvent> queue, MotionEvent event) {
+ queue.add(MotionEvent.obtain(event));
+ // Prune old events
+ while (!queue.isEmpty() && (event.getEventTime() - queue.peek().getEventTime() > 5000)) {
+ queue.remove().recycle();
+ }
}
private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
@@ -544,6 +588,9 @@
@Override
public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+
+ // Ensure that the state at the end of delegation is consistent with the last delegated
+ // UP/DOWN event in queue: still delegating if pointer is down, detecting otherwise
switch (event.getActionMasked()) {
case ACTION_UP:
case ACTION_CANCEL: {
@@ -551,9 +598,11 @@
} break;
case ACTION_DOWN: {
+ transitionTo(mDelegatingState);
mLastDelegatedDownEventTime = event.getDownTime();
} break;
}
+
if (getNext() != null) {
// We cache some events to see if the user wants to trigger magnification.
// If no magnification is triggered we inject these events with adjusted
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 07b0b77..eb031f3 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -1066,7 +1066,7 @@
int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP,
packageName, getServicePackageName())
- .setCounterValue(intDuration));
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration));
}
}
@@ -1090,7 +1090,7 @@
mMetricsLogger.write(new LogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY)
.setComponentName(componentName)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName())
- .setCounterValue(intDuration));
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration));
}
}
diff --git a/services/backup/java/com/android/server/backup/DataChangedJournal.java b/services/backup/java/com/android/server/backup/DataChangedJournal.java
index 9360c85..c2d3829 100644
--- a/services/backup/java/com/android/server/backup/DataChangedJournal.java
+++ b/services/backup/java/com/android/server/backup/DataChangedJournal.java
@@ -33,7 +33,7 @@
* <p>This information is persisted to the filesystem so that it is not lost in the event of a
* reboot.
*/
-public final class DataChangedJournal {
+public class DataChangedJournal {
private static final String FILE_NAME_PREFIX = "journal";
/**
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index b38b25a..42785be 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -153,8 +153,9 @@
OP_TYPE_BACKUP_WAIT);
// Start backup and wait for BackupManagerService to get callback for success or timeout
- agent.doBackup(mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
- mBackupManagerService.getBackupManagerBinder());
+ agent.doBackup(
+ mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
+ mBackupManagerService.getBackupManagerBinder(), /*transportFlags=*/ 0);
if (!mBackupManagerService.waitUntilOperationComplete(token)) {
Slog.e(TAG, "Key-value backup failed on package " + packageName);
return false;
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 465bb09..dc20d31 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -65,6 +65,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
+import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
@@ -387,7 +388,7 @@
mWakelock = wakelock;
}
- public BackupHandler getBackupHandler() {
+ public Handler getBackupHandler() {
return mBackupHandler;
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index 3bf77e8..d460f4d 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -74,6 +74,7 @@
PackageInfo mPkg;
private final long mQuota;
private final int mOpToken;
+ private final int mTransportFlags;
class FullBackupRunner implements Runnable {
@@ -100,7 +101,8 @@
@Override
public void run() {
try {
- FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
+ FullBackupDataOutput output = new FullBackupDataOutput(
+ mPipe, -1, mTransportFlags);
if (mWriteManifest) {
final boolean writeWidgetData = mWidgetData != null;
@@ -147,7 +149,7 @@
mTimeoutMonitor /* in parent class */,
OP_TYPE_BACKUP_WAIT);
mAgent.doFullBackup(mPipe, mQuota, mToken,
- backupManagerService.getBackupManagerBinder());
+ backupManagerService.getBackupManagerBinder(), mTransportFlags);
} catch (IOException e) {
Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
} catch (RemoteException e) {
@@ -164,7 +166,8 @@
public FullBackupEngine(RefactoredBackupManagerService backupManagerService,
OutputStream output,
FullBackupPreflight preflightHook, PackageInfo pkg,
- boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken) {
+ boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken,
+ int transportFlags) {
this.backupManagerService = backupManagerService;
mOutput = output;
mPreflightHook = preflightHook;
@@ -176,6 +179,7 @@
mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
mQuota = quota;
mOpToken = opToken;
+ mTransportFlags = transportFlags;
}
public int preflightCheck() throws RemoteException {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index f0b3e4a..19e601b 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -410,7 +410,8 @@
SHARED_BACKUP_AGENT_PACKAGE);
mBackupEngine = new FullBackupEngine(backupManagerService, out,
- null, pkg, mIncludeApks, this, Long.MAX_VALUE, mCurrentOpToken);
+ null, pkg, mIncludeApks, this, Long.MAX_VALUE,
+ mCurrentOpToken, /*transportFlags=*/ 0);
sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
// Don't need to check preflight result as there is no preflight hook.
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index d5b3d98..d04be12 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -378,7 +378,8 @@
enginePipes = ParcelFileDescriptor.createPipe();
mBackupRunner =
new SinglePackageBackupRunner(enginePipes[1], currentPackage,
- mTransportClient, quota, mBackupRunnerOpToken);
+ mTransportClient, quota, mBackupRunnerOpToken,
+ transport.getTransportFlags());
// The runner dup'd the pipe half, so we close it here
enginePipes[1].close();
enginePipes[1] = null;
@@ -681,12 +682,17 @@
final TransportClient mTransportClient;
final long mQuota;
private final int mCurrentOpToken;
+ private final int mTransportFlags;
SinglePackageBackupPreflight(
- TransportClient transportClient, long quota, int currentOpToken) {
+ TransportClient transportClient,
+ long quota,
+ int currentOpToken,
+ int transportFlags) {
mTransportClient = transportClient;
mQuota = quota;
mCurrentOpToken = currentOpToken;
+ mTransportFlags = transportFlags;
}
@Override
@@ -700,7 +706,7 @@
Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
}
agent.doMeasureFullBackup(mQuota, mCurrentOpToken,
- backupManagerService.getBackupManagerBinder());
+ backupManagerService.getBackupManagerBinder(), mTransportFlags);
// Now wait to get our result back. If this backstop timeout is reached without
// the latch being thrown, flow will continue as though a result or "normal"
@@ -785,20 +791,23 @@
private volatile int mBackupResult;
private final long mQuota;
private volatile boolean mIsCancelled;
+ private final int mTransportFlags;
SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
- TransportClient transportClient, long quota, int currentOpToken)
+ TransportClient transportClient, long quota, int currentOpToken, int transportFlags)
throws IOException {
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
mCurrentOpToken = currentOpToken;
mEphemeralToken = backupManagerService.generateRandomIntegerToken();
- mPreflight = new SinglePackageBackupPreflight(transportClient, quota, mEphemeralToken);
+ mPreflight = new SinglePackageBackupPreflight(
+ transportClient, quota, mEphemeralToken, transportFlags);
mPreflightLatch = new CountDownLatch(1);
mBackupLatch = new CountDownLatch(1);
mPreflightResult = BackupTransport.AGENT_ERROR;
mBackupResult = BackupTransport.AGENT_ERROR;
mQuota = quota;
+ mTransportFlags = transportFlags;
registerTask();
}
@@ -819,7 +828,7 @@
public void run() {
FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
mEngine = new FullBackupEngine(backupManagerService, out, mPreflight, mTarget, false,
- this, mQuota, mCurrentOpToken);
+ this, mQuota, mCurrentOpToken, mTransportFlags);
try {
try {
if (!mIsCancelled) {
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index 99ffa12..bacb357 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -714,8 +714,9 @@
mEphemeralOpToken, TIMEOUT_BACKUP_INTERVAL, this, OP_TYPE_BACKUP_WAIT);
backupManagerService.addBackupTrace("calling agent doBackup()");
- agent.doBackup(mSavedState, mBackupData, mNewState, quota, mEphemeralOpToken,
- backupManagerService.getBackupManagerBinder());
+ agent.doBackup(
+ mSavedState, mBackupData, mNewState, quota, mEphemeralOpToken,
+ backupManagerService.getBackupManagerBinder(), transport.getTransportFlags());
} catch (Exception e) {
Slog.e(TAG, "Error invoking for backup on " + packageName + ". " + e);
backupManagerService.addBackupTrace("exception: " + e);
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index 7ae5b43..82106ec 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -30,6 +30,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Binder;
+import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.util.Slog;
@@ -374,7 +375,7 @@
}
// Stop the session timeout until we finalize the restore
- BackupHandler backupHandler = mBackupManagerService.getBackupHandler();
+ Handler backupHandler = mBackupManagerService.getBackupHandler();
backupHandler.removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
PowerManager.WakeLock wakelock = mBackupManagerService.getWakelock();
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java
index 7b2e3df..1a2ca92 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -33,7 +33,7 @@
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.EventLog;
-import android.util.Log;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -41,9 +41,13 @@
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.TransportUtils.Priority;
+
+import dalvik.system.CloseGuard;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -70,17 +74,20 @@
* @see TransportManager
*/
public class TransportClient {
- private static final String TAG = "TransportClient";
+ @VisibleForTesting static final String TAG = "TransportClient";
private static final int LOG_BUFFER_SIZE = 5;
private final Context mContext;
private final Intent mBindIntent;
+ private final ServiceConnection mConnection;
private final String mIdentifier;
+ private final String mCreatorLogString;
private final ComponentName mTransportComponent;
private final Handler mListenerHandler;
private final String mPrefixForLog;
private final Object mStateLock = new Object();
private final Object mLogBufferLock = new Object();
+ private final CloseGuard mCloseGuard = CloseGuard.get();
@GuardedBy("mLogBufferLock")
private final List<String> mLogBuffer = new LinkedList<>();
@@ -99,12 +106,14 @@
Context context,
Intent bindIntent,
ComponentName transportComponent,
- String identifier) {
+ String identifier,
+ String caller) {
this(
context,
bindIntent,
transportComponent,
identifier,
+ caller,
new Handler(Looper.getMainLooper()));
}
@@ -114,79 +123,27 @@
Intent bindIntent,
ComponentName transportComponent,
String identifier,
+ String caller,
Handler listenerHandler) {
mContext = context;
mTransportComponent = transportComponent;
mBindIntent = bindIntent;
mIdentifier = identifier;
+ mCreatorLogString = caller;
mListenerHandler = listenerHandler;
+ mConnection = new TransportConnection(context, this);
// For logging
String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", "");
mPrefixForLog = classNameForLog + "#" + mIdentifier + ":";
+
+ mCloseGuard.open("markAsDisposed");
}
public ComponentName getTransportComponent() {
return mTransportComponent;
}
- // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one
- // of these calls, if a binding happen again the new service can be a different instance. Since
- // transports are stateful, we don't want a new instance responding for an old instance's state.
- private ServiceConnection mConnection =
- new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName componentName, IBinder binder) {
- IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
- synchronized (mStateLock) {
- checkStateIntegrityLocked();
-
- if (mState != State.UNUSABLE) {
- log(Log.DEBUG, "Transport connected");
- setStateLocked(State.CONNECTED, transport);
- notifyListenersAndClearLocked(transport);
- }
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName componentName) {
- synchronized (mStateLock) {
- log(Log.ERROR, "Service disconnected: client UNUSABLE");
- setStateLocked(State.UNUSABLE, null);
- // After unbindService() no calls back to mConnection
- mContext.unbindService(this);
- }
- }
-
- @Override
- public void onBindingDied(ComponentName name) {
- synchronized (mStateLock) {
- checkStateIntegrityLocked();
-
- log(Log.ERROR, "Binding died: client UNUSABLE");
- // After unbindService() no calls back to mConnection
- switch (mState) {
- case State.UNUSABLE:
- break;
- case State.IDLE:
- log(Log.ERROR, "Unexpected state transition IDLE => UNUSABLE");
- setStateLocked(State.UNUSABLE, null);
- break;
- case State.BOUND_AND_CONNECTING:
- setStateLocked(State.UNUSABLE, null);
- mContext.unbindService(this);
- notifyListenersAndClearLocked(null);
- break;
- case State.CONNECTED:
- setStateLocked(State.UNUSABLE, null);
- mContext.unbindService(this);
- break;
- }
- }
- }
- };
-
/**
* Attempts to connect to the transport (if needed).
*
@@ -240,7 +197,7 @@
switch (mState) {
case State.UNUSABLE:
- log(Log.WARN, caller, "Async connect: UNUSABLE client");
+ log(Priority.WARN, caller, "Async connect: UNUSABLE client");
notifyListener(listener, null, caller);
break;
case State.IDLE:
@@ -254,22 +211,25 @@
// We don't need to set a time-out because we are guaranteed to get a call
// back in ServiceConnection, either an onServiceConnected() or
// onBindingDied().
- log(Log.DEBUG, caller, "Async connect: service bound, connecting");
+ log(Priority.DEBUG, caller, "Async connect: service bound, connecting");
setStateLocked(State.BOUND_AND_CONNECTING, null);
mListeners.put(listener, caller);
} else {
- log(Log.ERROR, "Async connect: bindService returned false");
+ log(Priority.ERROR, "Async connect: bindService returned false");
// mState remains State.IDLE
mContext.unbindService(mConnection);
notifyListener(listener, null, caller);
}
break;
case State.BOUND_AND_CONNECTING:
- log(Log.DEBUG, caller, "Async connect: already connecting, adding listener");
+ log(
+ Priority.DEBUG,
+ caller,
+ "Async connect: already connecting, adding listener");
mListeners.put(listener, caller);
break;
case State.CONNECTED:
- log(Log.DEBUG, caller, "Async connect: reusing transport");
+ log(Priority.DEBUG, caller, "Async connect: reusing transport");
notifyListener(listener, mTransport, caller);
break;
}
@@ -286,7 +246,7 @@
synchronized (mStateLock) {
checkStateIntegrityLocked();
- log(Log.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")");
+ log(Priority.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")");
switch (mState) {
case State.UNUSABLE:
case State.IDLE:
@@ -305,6 +265,15 @@
}
}
+ /** Marks this TransportClient as disposed, allowing it to be GC'ed without warnings. */
+ public void markAsDisposed() {
+ synchronized (mStateLock) {
+ Preconditions.checkState(
+ mState < State.BOUND_AND_CONNECTING, "Can't mark as disposed if still bound");
+ mCloseGuard.close();
+ }
+ }
+
/**
* Attempts to connect to the transport (if needed) and returns it.
*
@@ -335,14 +304,14 @@
IBackupTransport transport = mTransport;
if (transport != null) {
- log(Log.INFO, caller, "Sync connect: reusing transport");
+ log(Priority.DEBUG, caller, "Sync connect: reusing transport");
return transport;
}
// If it's already UNUSABLE we return straight away, no need to go to main-thread
synchronized (mStateLock) {
if (mState == State.UNUSABLE) {
- log(Log.WARN, caller, "Sync connect: UNUSABLE client");
+ log(Priority.WARN, caller, "Sync connect: UNUSABLE client");
return null;
}
}
@@ -352,14 +321,14 @@
(requestedTransport, transportClient) ->
transportFuture.complete(requestedTransport);
- log(Log.DEBUG, caller, "Sync connect: calling async");
+ log(Priority.DEBUG, caller, "Sync connect: calling async");
connectAsync(requestListener, caller);
try {
return transportFuture.get();
} catch (InterruptedException | ExecutionException e) {
String error = e.getClass().getSimpleName();
- log(Log.ERROR, caller, error + " while waiting for transport: " + e.getMessage());
+ log(Priority.ERROR, caller, error + " while waiting for transport: " + e.getMessage());
return null;
}
}
@@ -379,7 +348,7 @@
public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
IBackupTransport transport = connect(caller);
if (transport == null) {
- log(Log.ERROR, caller, "Transport connection failed");
+ log(Priority.ERROR, caller, "Transport connection failed");
throw new TransportNotAvailableException();
}
return transport;
@@ -398,7 +367,7 @@
throws TransportNotAvailableException {
IBackupTransport transport = mTransport;
if (transport == null) {
- log(Log.ERROR, caller, "Transport not connected");
+ log(Priority.ERROR, caller, "Transport not connected");
throw new TransportNotAvailableException();
}
return transport;
@@ -413,12 +382,92 @@
+ "}";
}
+ @Override
+ protected void finalize() throws Throwable {
+ synchronized (mStateLock) {
+ mCloseGuard.warnIfOpen();
+ if (mState >= State.BOUND_AND_CONNECTING) {
+ String callerLogString = "TransportClient.finalize()";
+ log(
+ Priority.ERROR,
+ callerLogString,
+ "Dangling TransportClient created in [" + mCreatorLogString + "] being "
+ + "GC'ed. Left bound, unbinding...");
+ try {
+ unbind(callerLogString);
+ } catch (IllegalStateException e) {
+ // May throw because there may be a race between this method being called and
+ // the framework calling any method on the connection with the weak reference
+ // there already cleared. In this case the connection will unbind before this
+ // is called. This is fine.
+ }
+ }
+ }
+ }
+
+ private void onServiceConnected(IBinder binder) {
+ IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ if (mState != State.UNUSABLE) {
+ log(Priority.DEBUG, "Transport connected");
+ setStateLocked(State.CONNECTED, transport);
+ notifyListenersAndClearLocked(transport);
+ }
+ }
+ }
+
+ /**
+ * If we are called here the TransportClient becomes UNUSABLE. After one of these calls, if a
+ * binding happen again the new service can be a different instance. Since transports are
+ * stateful, we don't want a new instance responding for an old instance's state.
+ */
+ private void onServiceDisconnected() {
+ synchronized (mStateLock) {
+ log(Priority.ERROR, "Service disconnected: client UNUSABLE");
+ setStateLocked(State.UNUSABLE, null);
+ // After unbindService() no calls back to mConnection
+ mContext.unbindService(mConnection);
+ }
+ }
+
+ /**
+ * If we are called here the TransportClient becomes UNUSABLE for the same reason as in {@link
+ * #onServiceDisconnected()}.
+ */
+ private void onBindingDied() {
+ synchronized (mStateLock) {
+ checkStateIntegrityLocked();
+
+ log(Priority.ERROR, "Binding died: client UNUSABLE");
+ // After unbindService() no calls back to mConnection
+ switch (mState) {
+ case State.UNUSABLE:
+ break;
+ case State.IDLE:
+ log(Priority.ERROR, "Unexpected state transition IDLE => UNUSABLE");
+ setStateLocked(State.UNUSABLE, null);
+ break;
+ case State.BOUND_AND_CONNECTING:
+ setStateLocked(State.UNUSABLE, null);
+ mContext.unbindService(mConnection);
+ notifyListenersAndClearLocked(null);
+ break;
+ case State.CONNECTED:
+ setStateLocked(State.UNUSABLE, null);
+ mContext.unbindService(mConnection);
+ break;
+ }
+ }
+ }
+
private void notifyListener(
TransportConnectionListener listener,
@Nullable IBackupTransport transport,
String caller) {
String transportString = (transport != null) ? "IBackupTransport" : "null";
- log(Log.INFO, "Notifying [" + caller + "] transport = " + transportString);
+ log(Priority.INFO, "Notifying [" + caller + "] transport = " + transportString);
mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
}
@@ -434,7 +483,7 @@
@GuardedBy("mStateLock")
private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
- log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
+ log(Priority.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
onStateTransition(mState, state);
mState = state;
mTransport = transport;
@@ -503,7 +552,7 @@
private void checkState(boolean assertion, String message) {
if (!assertion) {
- log(Log.ERROR, message);
+ log(Priority.ERROR, message);
}
}
@@ -560,9 +609,63 @@
@IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED})
@Retention(RetentionPolicy.SOURCE)
private @interface State {
+ // Constant values MUST be in order
int UNUSABLE = 0;
int IDLE = 1;
int BOUND_AND_CONNECTING = 2;
int CONNECTED = 3;
}
+
+ /**
+ * This class is a proxy to TransportClient methods that doesn't hold a strong reference to the
+ * TransportClient, allowing it to be GC'ed. If the reference was lost it logs a message.
+ */
+ private static class TransportConnection implements ServiceConnection {
+ private final Context mContext;
+ private final WeakReference<TransportClient> mTransportClientRef;
+
+ private TransportConnection(Context context, TransportClient transportClient) {
+ mContext = context;
+ mTransportClientRef = new WeakReference<>(transportClient);
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName transportComponent, IBinder binder) {
+ TransportClient transportClient = mTransportClientRef.get();
+ if (transportClient == null) {
+ referenceLost("TransportConnection.onServiceConnected()");
+ return;
+ }
+ transportClient.onServiceConnected(binder);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName transportComponent) {
+ TransportClient transportClient = mTransportClientRef.get();
+ if (transportClient == null) {
+ referenceLost("TransportConnection.onServiceDisconnected()");
+ return;
+ }
+ transportClient.onServiceDisconnected();
+ }
+
+ @Override
+ public void onBindingDied(ComponentName transportComponent) {
+ TransportClient transportClient = mTransportClientRef.get();
+ if (transportClient == null) {
+ referenceLost("TransportConnection.onBindingDied()");
+ return;
+ }
+ transportClient.onBindingDied();
+ }
+
+ /** @see TransportClient#finalize() */
+ private void referenceLost(String caller) {
+ mContext.unbindService(this);
+ TransportUtils.log(
+ Priority.INFO,
+ TAG,
+ caller + " called but TransportClient reference has been GC'ed");
+ }
+ }
}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
index 1132bce..4041932 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
@@ -22,8 +22,10 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.util.Log;
+
import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.TransportUtils.Priority;
+
import java.io.PrintWriter;
import java.util.Map;
import java.util.WeakHashMap;
@@ -63,11 +65,14 @@
mContext,
bindIntent,
transportComponent,
- Integer.toString(mTransportClientsCreated));
+ Integer.toString(mTransportClientsCreated),
+ caller);
mTransportClientsCallerMap.put(transportClient, caller);
mTransportClientsCreated++;
TransportUtils.log(
- Log.DEBUG, TAG, formatMessage(null, caller, "Retrieving " + transportClient));
+ Priority.DEBUG,
+ TAG,
+ formatMessage(null, caller, "Retrieving " + transportClient));
return transportClient;
}
}
@@ -82,9 +87,12 @@
*/
public void disposeOfTransportClient(TransportClient transportClient, String caller) {
transportClient.unbind(caller);
+ transportClient.markAsDisposed();
synchronized (mTransportClientsLock) {
TransportUtils.log(
- Log.DEBUG, TAG, formatMessage(null, caller, "Disposing of " + transportClient));
+ Priority.DEBUG,
+ TAG,
+ formatMessage(null, caller, "Disposing of " + transportClient));
mTransportClientsCallerMap.remove(transportClient);
}
}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportUtils.java b/services/backup/java/com/android/server/backup/transport/TransportUtils.java
index 56b2d44..766d77b 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportUtils.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportUtils.java
@@ -16,6 +16,7 @@
package com.android.server.backup.transport;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.os.DeadObjectException;
import android.util.Log;
@@ -23,6 +24,9 @@
import com.android.internal.backup.IBackupTransport;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/** Utility methods for transport-related operations. */
public class TransportUtils {
private static final String TAG = "TransportUtils";
@@ -34,14 +38,16 @@
public static IBackupTransport checkTransportNotNull(@Nullable IBackupTransport transport)
throws TransportNotAvailableException {
if (transport == null) {
- log(Log.ERROR, TAG, "Transport not available");
+ log(Priority.ERROR, TAG, "Transport not available");
throw new TransportNotAvailableException();
}
return transport;
}
- static void log(int priority, String tag, String message) {
- if (Log.isLoggable(tag, priority)) {
+ static void log(@Priority int priority, String tag, String message) {
+ if (priority == Priority.WTF) {
+ Slog.wtf(tag, message);
+ } else if (Log.isLoggable(tag, priority)) {
Slog.println(priority, tag, message);
}
}
@@ -57,5 +63,20 @@
return string.append(message).toString();
}
+ /**
+ * Create our own constants so we can log WTF using the same APIs. Except for {@link
+ * Priority#WTF} all the others have the same value, so can be used directly
+ */
+ @IntDef({Priority.VERBOSE, Priority.DEBUG, Priority.INFO, Priority.WARN, Priority.WTF})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Priority {
+ int VERBOSE = Log.VERBOSE;
+ int DEBUG = Log.DEBUG;
+ int INFO = Log.INFO;
+ int WARN = Log.WARN;
+ int ERROR = Log.ERROR;
+ int WTF = -1;
+ }
+
private TransportUtils() {}
}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 15c0f3c..342b48e 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -82,6 +82,7 @@
import java.util.TreeSet;
import java.util.function.Predicate;
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.app.AlarmManager.RTC;
import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
@@ -175,7 +176,6 @@
long mNextNonWakeupDeliveryTime;
long mLastTimeChangeClockTime;
long mLastTimeChangeRealtime;
- long mAllowWhileIdleMinTime;
int mNumTimeChanged;
// Bookkeeping about the identity of the "System UI" package, determined at runtime.
@@ -199,6 +199,12 @@
*/
final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
+ /**
+ * For each uid, we store whether the last allow-while-idle alarm was dispatched while
+ * the uid was in foreground or not. We will use the allow_while_idle_short_time in such cases.
+ */
+ final SparseBooleanArray mUseAllowWhileIdleShortTime = new SparseBooleanArray();
+
final static class IdleDispatchEntry {
int uid;
String pkg;
@@ -242,7 +248,6 @@
private static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
= "allow_while_idle_whitelist_duration";
private static final String KEY_LISTENER_TIMEOUT = "listener_timeout";
- private static final String KEY_BG_RESTRICTIONS_ENABLED = "limit_bg_alarms_enabled";
private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
@@ -277,7 +282,6 @@
public Constants(Handler handler) {
super(handler);
- updateAllowWhileIdleMinTimeLocked();
updateAllowWhileIdleWhitelistDurationLocked();
}
@@ -288,11 +292,6 @@
updateConstants();
}
- public void updateAllowWhileIdleMinTimeLocked() {
- mAllowWhileIdleMinTime = mPendingIdleUntil != null
- ? ALLOW_WHILE_IDLE_LONG_TIME : ALLOW_WHILE_IDLE_SHORT_TIME;
- }
-
public void updateAllowWhileIdleWhitelistDurationLocked() {
if (mLastAllowWhileIdleWhitelistDuration != ALLOW_WHILE_IDLE_WHITELIST_DURATION) {
mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION;
@@ -330,7 +329,6 @@
LISTENER_TIMEOUT = mParser.getLong(KEY_LISTENER_TIMEOUT,
DEFAULT_LISTENER_TIMEOUT);
- updateAllowWhileIdleMinTimeLocked();
updateAllowWhileIdleWhitelistDurationLocked();
}
}
@@ -967,9 +965,6 @@
}
}
- // Make sure we are using the correct ALLOW_WHILE_IDLE min time.
- mConstants.updateAllowWhileIdleMinTimeLocked();
-
// Reschedule everything.
rescheduleKernelAlarmsLocked();
updateNextAlarmClockLocked();
@@ -1421,7 +1416,6 @@
return;
}
}
-
if (RECORD_DEVICE_IDLE_ALARMS) {
if ((a.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
IdleDispatchEntry ent = new IdleDispatchEntry();
@@ -1472,7 +1466,6 @@
}
mPendingIdleUntil = a;
- mConstants.updateAllowWhileIdleMinTimeLocked();
needRebatch = true;
} else if ((a.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
if (mNextWakeFromIdle == null || mNextWakeFromIdle.whenElapsed > a.whenElapsed) {
@@ -1751,6 +1744,15 @@
if (!blocked) {
pw.println(" none");
}
+ pw.print(" mUseAllowWhileIdleShortTime: [");
+ for (int i = 0; i < mUseAllowWhileIdleShortTime.size(); i++) {
+ if (mUseAllowWhileIdleShortTime.valueAt(i)) {
+ UserHandle.formatUid(pw, mUseAllowWhileIdleShortTime.keyAt(i));
+ pw.print(" ");
+ }
+ }
+ pw.println("]");
+
if (mPendingIdleUntil != null || mPendingWhileIdleAlarms.size() > 0) {
pw.println();
pw.println(" Idle mode state:");
@@ -1803,9 +1805,6 @@
pw.println();
}
- pw.print(" mAllowWhileIdleMinTime=");
- TimeUtils.formatDuration(mAllowWhileIdleMinTime, pw);
- pw.println();
if (mLastAllowWhileIdleDispatch.size() > 0) {
pw.println(" Last allow while idle dispatch times:");
for (int i=0; i<mLastAllowWhileIdleDispatch.size(); i++) {
@@ -2072,8 +2071,6 @@
f.writeToProto(proto, AlarmManagerServiceProto.OUTSTANDING_DELIVERIES);
}
- proto.write(AlarmManagerServiceProto.ALLOW_WHILE_IDLE_MIN_DURATION_MS,
- mAllowWhileIdleMinTime);
for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); ++i) {
final long token = proto.start(
AlarmManagerServiceProto.LAST_ALLOW_WHILE_IDLE_DISPATCH_TIMES);
@@ -2739,6 +2736,7 @@
}
private boolean isBackgroundRestricted(Alarm alarm) {
+ final boolean allowWhileIdle = (alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0;
if (alarm.alarmClock != null) {
// Don't block alarm clocks
return false;
@@ -2751,7 +2749,8 @@
final String sourcePackage =
(alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName;
final int sourceUid = alarm.creatorUid;
- return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage);
+ return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage,
+ allowWhileIdle);
}
private native long init();
@@ -2785,8 +2784,21 @@
if ((alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
// If this is an ALLOW_WHILE_IDLE alarm, we constrain how frequently the app can
// schedule such alarms.
- long lastTime = mLastAllowWhileIdleDispatch.get(alarm.uid, 0);
- long minTime = lastTime + mAllowWhileIdleMinTime;
+ final long lastTime = mLastAllowWhileIdleDispatch.get(alarm.creatorUid, 0);
+ final boolean dozing = mPendingIdleUntil != null;
+ final boolean ebs = mForceAppStandbyTracker.isForceAllAppsStandbyEnabled();
+ final long minTime;
+ if (!dozing && !ebs) {
+ minTime = lastTime + mConstants.ALLOW_WHILE_IDLE_SHORT_TIME;
+ } else if (dozing) {
+ minTime = lastTime + mConstants.ALLOW_WHILE_IDLE_LONG_TIME;
+ } else if (mUseAllowWhileIdleShortTime.get(alarm.creatorUid)) {
+ // if the last allow-while-idle went off while uid was fg, or the uid
+ // recently came into fg, don't block the alarm for long.
+ minTime = lastTime + mConstants.ALLOW_WHILE_IDLE_SHORT_TIME;
+ } else {
+ minTime = lastTime + mConstants.ALLOW_WHILE_IDLE_LONG_TIME;
+ }
if (nowELAPSED < minTime) {
// Whoops, it hasn't been long enough since the last ALLOW_WHILE_IDLE
// alarm went off for this app. Reschedule the alarm to be in the
@@ -3526,6 +3538,7 @@
@Override public void onUidGone(int uid, boolean disabled) {
synchronized (mLock) {
+ mUseAllowWhileIdleShortTime.delete(uid);
if (disabled) {
removeForStoppedLocked(uid);
}
@@ -3533,6 +3546,9 @@
}
@Override public void onUidActive(int uid) {
+ synchronized (mLock) {
+ mUseAllowWhileIdleShortTime.put(uid, true);
+ }
}
@Override public void onUidIdle(int uid, boolean disabled) {
@@ -3547,7 +3563,6 @@
}
};
-
private final Listener mForceAppStandbyListener = new Listener() {
@Override
public void unblockAllUnrestrictedAlarms() {
@@ -3829,7 +3844,12 @@
if (allowWhileIdle) {
// Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
- mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
+ mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED);
+ if (mForceAppStandbyTracker.isInForeground(alarm.creatorUid)) {
+ mUseAllowWhileIdleShortTime.put(alarm.creatorUid, true);
+ } else {
+ mUseAllowWhileIdleShortTime.put(alarm.creatorUid, false);
+ }
if (RECORD_DEVICE_IDLE_ALARMS) {
IdleDispatchEntry ent = new IdleDispatchEntry();
ent.uid = alarm.uid;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 145b307..3bfa31a 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -140,6 +140,7 @@
import com.android.server.connectivity.KeepaliveTracker;
import com.android.server.connectivity.LingerMonitor;
import com.android.server.connectivity.MockableSystemProperties;
+import com.android.server.connectivity.MultipathPolicyTracker;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkDiagnostics;
import com.android.server.connectivity.NetworkMonitor;
@@ -511,6 +512,9 @@
@VisibleForTesting
final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
+ @VisibleForTesting
+ final MultipathPolicyTracker mMultipathPolicyTracker;
+
/**
* Implements support for the legacy "one network per network type" model.
*
@@ -894,6 +898,8 @@
mContext, mHandler, () -> rematchForAvoidBadWifiUpdate());
mMultinetworkPolicyTracker.start();
+ mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler);
+
mDnsManager = new DnsManager(mContext, mNetd, mSystemProperties);
registerPrivateDnsSettingsCallbacks();
}
@@ -1974,6 +1980,9 @@
pw.println();
dumpAvoidBadWifiSettings(pw);
+ pw.println();
+ mMultipathPolicyTracker.dump(pw);
+
if (argsContain(args, SHORT_ARG) == false) {
pw.println();
synchronized (mValidationLogs) {
@@ -2891,6 +2900,11 @@
return ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED;
}
+ Integer networkPreference = mMultipathPolicyTracker.getMultipathPreference(network);
+ if (networkPreference != null) {
+ return networkPreference;
+ }
+
return mMultinetworkPolicyTracker.getMeteredMultipathPreference();
}
@@ -2984,6 +2998,7 @@
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
nai.networkMonitor.systemReady = true;
}
+ mMultipathPolicyTracker.start();
break;
}
case EVENT_REVALIDATE_NETWORK: {
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index 792fdfe..257845e 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -214,8 +214,11 @@
int uid, @NonNull String packageName) {
updateJobsForUidPackage(uid, packageName);
- if (!sender.areAlarmsRestricted(uid, packageName)) {
+ if (!sender.areAlarmsRestricted(uid, packageName, /*allowWhileIdle=*/ false)) {
unblockAlarmsForUidPackage(uid, packageName);
+ } else if (!sender.areAlarmsRestricted(uid, packageName, /*allowWhileIdle=*/ true)){
+ // we need to deliver the allow-while-idle alarms for this uid, package
+ unblockAllUnrestrictedAlarms();
}
}
@@ -706,7 +709,7 @@
synchronized (mLock) {
unblockAlarms = !mForcedAppStandbyEnabled && !mForceAllAppsStandby;
}
- for (Listener l: cloneListeners()) {
+ for (Listener l : cloneListeners()) {
l.updateAllJobs();
if (unblockAlarms) {
l.unblockAllUnrestrictedAlarms();
@@ -818,9 +821,10 @@
/**
* @return whether alarms should be restricted for a UID package-name.
*/
- public boolean areAlarmsRestricted(int uid, @NonNull String packageName) {
+ public boolean areAlarmsRestricted(int uid, @NonNull String packageName,
+ boolean allowWhileIdle) {
return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false,
- /* exemptOnBatterySaver =*/ false);
+ /* exemptOnBatterySaver =*/ allowWhileIdle);
}
/**
@@ -879,7 +883,6 @@
/**
* @return whether force all apps standby is enabled or not.
*
- * Note clients normally shouldn't need to access it.
*/
boolean isForceAllAppsStandbyEnabled() {
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 5a4f7ca..bd93b179 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -67,6 +67,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -829,7 +830,7 @@
}
mAllowedResolutionLevel = getAllowedResolutionLevel(pid, uid);
mIdentity = new Identity(uid, pid, packageName);
- if (workSource != null && workSource.size() <= 0) {
+ if (workSource != null && workSource.isEmpty()) {
workSource = null;
}
mWorkSource = workSource;
@@ -1814,13 +1815,11 @@
if (locationRequest.getInterval() <= thresholdInterval) {
if (record.mReceiver.mWorkSource != null
- && record.mReceiver.mWorkSource.size() > 0
- && record.mReceiver.mWorkSource.getName(0) != null) {
- // Assign blame to another work source.
- // Can only assign blame if the WorkSource contains names.
+ && isValidWorkSource(record.mReceiver.mWorkSource)) {
worksource.add(record.mReceiver.mWorkSource);
} else {
- // Assign blame to caller.
+ // Assign blame to caller if there's no WorkSource associated with
+ // the request or if it's invalid.
worksource.add(
record.mReceiver.mIdentity.mUid,
record.mReceiver.mIdentity.mPackageName);
@@ -1835,6 +1834,23 @@
p.setRequest(providerRequest, worksource);
}
+ /**
+ * Whether a given {@code WorkSource} associated with a Location request is valid.
+ */
+ private static boolean isValidWorkSource(WorkSource workSource) {
+ if (workSource.size() > 0) {
+ // If the WorkSource has one or more non-chained UIDs, make sure they're accompanied
+ // by tags.
+ return workSource.getName(0) != null;
+ } else {
+ // For now, make sure callers have supplied an attribution tag for use with
+ // AppOpsManager. This might be relaxed in the future.
+ final ArrayList<WorkChain> workChains = workSource.getWorkChains();
+ return workChains != null && !workChains.isEmpty() &&
+ workChains.get(0).getAttributionTag() != null;
+ }
+ }
+
@Override
public String[] getBackgroundThrottlingWhitelist() {
synchronized (mLock) {
@@ -2057,7 +2073,7 @@
checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
request.getProvider());
WorkSource workSource = request.getWorkSource();
- if (workSource != null && workSource.size() > 0) {
+ if (workSource != null && !workSource.isEmpty()) {
checkDeviceStatsAllowed();
}
boolean hideFromAppOps = request.getHideFromAppOps();
@@ -2570,7 +2586,7 @@
// Check INTERACT_ACROSS_USERS permission if userId is not current user id.
checkInteractAcrossUsersPermission(userId);
- // enable all location providers
+ // Enable or disable all location providers
synchronized (mLock) {
for(String provider : getAllProviders()) {
setProviderEnabledForUser(provider, enabled, userId);
@@ -2586,10 +2602,10 @@
*/
@Override
public boolean isLocationEnabledForUser(int userId) {
-
// Check INTERACT_ACROSS_USERS permission if userId is not current user id.
checkInteractAcrossUsersPermission(userId);
+ // If at least one location provider is enabled, return true
synchronized (mLock) {
for (String provider : getAllProviders()) {
if (isProviderEnabledForUser(provider, userId)) {
@@ -2602,7 +2618,7 @@
@Override
public boolean isProviderEnabled(String provider) {
- return isProviderEnabledForUser(provider, UserHandle.myUserId());
+ return isProviderEnabledForUser(provider, UserHandle.getCallingUserId());
}
/**
diff --git a/services/core/java/com/android/server/MultipathPolicyTracker.java b/services/core/java/com/android/server/MultipathPolicyTracker.java
new file mode 100644
index 0000000..9e0a230
--- /dev/null
+++ b/services/core/java/com/android/server/MultipathPolicyTracker.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import android.app.usage.NetworkStatsManager;
+import android.app.usage.NetworkStatsManager.UsageCallback;
+import android.content.Context;
+import android.net.INetworkStatsService;
+import android.net.INetworkPolicyManager;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkRequest;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.net.StringNetworkSpecifier;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.TelephonyManager;
+import android.util.DebugUtils;
+import android.util.Slog;
+
+import java.util.Calendar;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
+import com.android.server.net.NetworkPolicyManagerInternal;
+
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
+import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
+
+/**
+ * Manages multipath data budgets.
+ *
+ * Informs the return value of ConnectivityManager#getMultipathPreference() based on:
+ * - The user's data plan, as returned by getSubscriptionOpportunisticQuota().
+ * - The amount of data usage that occurs on mobile networks while they are not the system default
+ * network (i.e., when the app explicitly selected such networks).
+ *
+ * Currently, quota is determined on a daily basis, from midnight to midnight local time.
+ *
+ * @hide
+ */
+public class MultipathPolicyTracker {
+ private static String TAG = MultipathPolicyTracker.class.getSimpleName();
+
+ private static final boolean DBG = false;
+
+ private final Context mContext;
+ private final Handler mHandler;
+
+ private ConnectivityManager mCM;
+ private NetworkStatsManager mStatsManager;
+ private NetworkPolicyManager mNPM;
+ private TelephonyManager mTelephonyManager;
+ private INetworkStatsService mStatsService;
+
+ private NetworkCallback mMobileNetworkCallback;
+ private NetworkPolicyManager.Listener mPolicyListener;
+
+ // STOPSHIP: replace this with a configurable mechanism.
+ private static final long DEFAULT_DAILY_MULTIPATH_QUOTA = 2_500_000;
+
+ private volatile int mMeteredMultipathPreference;
+
+ public MultipathPolicyTracker(Context ctx, Handler handler) {
+ mContext = ctx;
+ mHandler = handler;
+ // Because we are initialized by the ConnectivityService constructor, we can't touch any
+ // connectivity APIs. Service initialization is done in start().
+ }
+
+ public void start() {
+ mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mNPM = (NetworkPolicyManager) mContext.getSystemService(Context.NETWORK_POLICY_SERVICE);
+ mStatsManager = (NetworkStatsManager) mContext.getSystemService(
+ Context.NETWORK_STATS_SERVICE);
+ mStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+
+ registerTrackMobileCallback();
+ registerNetworkPolicyListener();
+ }
+
+ public void shutdown() {
+ maybeUnregisterTrackMobileCallback();
+ unregisterNetworkPolicyListener();
+ for (MultipathTracker t : mMultipathTrackers.values()) {
+ t.shutdown();
+ }
+ mMultipathTrackers.clear();
+ }
+
+ // Called on an arbitrary binder thread.
+ public Integer getMultipathPreference(Network network) {
+ MultipathTracker t = mMultipathTrackers.get(network);
+ if (t != null) {
+ return t.getMultipathPreference();
+ }
+ return null;
+ }
+
+ // Track information on mobile networks as they come and go.
+ class MultipathTracker {
+ final Network network;
+ final int subId;
+ final String subscriberId;
+
+ private long mQuota;
+ /** Current multipath budget. Nonzero iff we have budget and a UsageCallback is armed. */
+ private long mMultipathBudget;
+ private final NetworkTemplate mNetworkTemplate;
+ private final UsageCallback mUsageCallback;
+
+ public MultipathTracker(Network network, NetworkCapabilities nc) {
+ this.network = network;
+ try {
+ subId = Integer.parseInt(
+ ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString());
+ } catch (ClassCastException | NullPointerException | NumberFormatException e) {
+ throw new IllegalStateException(String.format(
+ "Can't get subId from mobile network %s (%s): %s",
+ network, nc, e.getMessage()));
+ }
+
+ TelephonyManager tele = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ if (tele == null) {
+ throw new IllegalStateException(String.format("Missing TelephonyManager"));
+ }
+ tele = tele.createForSubscriptionId(subId);
+ if (tele == null) {
+ throw new IllegalStateException(String.format(
+ "Can't get TelephonyManager for subId %d", subId));
+ }
+
+ subscriberId = tele.getSubscriberId();
+ mNetworkTemplate = new NetworkTemplate(
+ NetworkTemplate.MATCH_MOBILE_ALL, subscriberId, new String[] { subscriberId },
+ null, NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+ NetworkStats.DEFAULT_NETWORK_NO);
+ mUsageCallback = new UsageCallback() {
+ @Override
+ public void onThresholdReached(int networkType, String subscriberId) {
+ if (DBG) Slog.d(TAG, "onThresholdReached for network " + network);
+ mMultipathBudget = 0;
+ updateMultipathBudget();
+ }
+ };
+
+ updateMultipathBudget();
+ }
+
+ private long getDailyNonDefaultDataUsage() {
+ Calendar start = Calendar.getInstance();
+ Calendar end = (Calendar) start.clone();
+ start.set(Calendar.HOUR_OF_DAY, 0);
+ start.set(Calendar.MINUTE, 0);
+ start.set(Calendar.SECOND, 0);
+ start.set(Calendar.MILLISECOND, 0);
+
+ long bytes;
+ try {
+ // TODO: Consider using NetworkStatsManager.getSummaryForDevice instead.
+ bytes = mStatsService.getNetworkTotalBytes(mNetworkTemplate,
+ start.getTimeInMillis(), end.getTimeInMillis());
+ if (DBG) Slog.w(TAG, "Non-default data usage: " + bytes);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Can't fetch daily data usage: " + e);
+ bytes = -1;
+ } catch (IllegalStateException e) {
+ // Bandwidth control disabled?
+ bytes = -1;
+ }
+ return bytes;
+ }
+
+ void updateMultipathBudget() {
+ NetworkPolicyManagerInternal npms = LocalServices.getService(
+ NetworkPolicyManagerInternal.class);
+ long quota = npms.getSubscriptionOpportunisticQuota(this.network, QUOTA_TYPE_MULTIPATH);
+ if (DBG) Slog.d(TAG, "Opportunistic quota from data plan: " + quota + " bytes");
+
+ if (quota == 0) {
+ // STOPSHIP: replace this with a configurable mechanism.
+ quota = DEFAULT_DAILY_MULTIPATH_QUOTA;
+ if (DBG) Slog.d(TAG, "Setting quota: " + quota + " bytes");
+ }
+
+ if (haveMultipathBudget() && quota == mQuota) {
+ // If we already have a usage callback pending , there's no need to re-register it
+ // if the quota hasn't changed. The callback will simply fire as expected when the
+ // budget is spent. Also: if we re-register the callback when we're below the
+ // UsageCallback's minimum value of 2MB, we'll overshoot the budget.
+ if (DBG) Slog.d(TAG, "Quota still " + quota + ", not updating.");
+ return;
+ }
+ mQuota = quota;
+
+ long usage = getDailyNonDefaultDataUsage();
+ long budget = Math.max(0, quota - usage);
+ if (budget > 0) {
+ if (DBG) Slog.d(TAG, "Setting callback for " + budget +
+ " bytes on network " + network);
+ registerUsageCallback(budget);
+ } else {
+ maybeUnregisterUsageCallback();
+ }
+ }
+
+ public int getMultipathPreference() {
+ if (haveMultipathBudget()) {
+ return MULTIPATH_PREFERENCE_HANDOVER | MULTIPATH_PREFERENCE_RELIABILITY;
+ }
+ return 0;
+ }
+
+ // For debugging only.
+ public long getQuota() {
+ return mQuota;
+ }
+
+ // For debugging only.
+ public long getMultipathBudget() {
+ return mMultipathBudget;
+ }
+
+ private boolean haveMultipathBudget() {
+ return mMultipathBudget > 0;
+ }
+
+ private void registerUsageCallback(long budget) {
+ maybeUnregisterUsageCallback();
+ mStatsManager.registerUsageCallback(mNetworkTemplate, TYPE_MOBILE, budget,
+ mUsageCallback, mHandler);
+ mMultipathBudget = budget;
+ }
+
+ private void maybeUnregisterUsageCallback() {
+ if (haveMultipathBudget()) {
+ if (DBG) Slog.d(TAG, "Unregistering callback, budget was " + mMultipathBudget);
+ mStatsManager.unregisterUsageCallback(mUsageCallback);
+ mMultipathBudget = 0;
+ }
+ }
+
+ void shutdown() {
+ maybeUnregisterUsageCallback();
+ }
+ }
+
+ // Only ever updated on the handler thread. Accessed from other binder threads to retrieve
+ // the tracker for a specific network.
+ private final ConcurrentHashMap <Network, MultipathTracker> mMultipathTrackers =
+ new ConcurrentHashMap<>();
+
+ // TODO: this races with app code that might respond to onAvailable() by immediately calling
+ // getMultipathPreference. Fix this by adding to ConnectivityService the ability to directly
+ // invoke NetworkCallbacks on tightly-coupled classes such as this one which run on its
+ // handler thread.
+ private void registerTrackMobileCallback() {
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .build();
+ mMobileNetworkCallback = new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+ MultipathTracker existing = mMultipathTrackers.get(network);
+ if (existing != null) {
+ existing.updateMultipathBudget();
+ return;
+ }
+
+ try {
+ mMultipathTrackers.put(network, new MultipathTracker(network, nc));
+ } catch (IllegalStateException e) {
+ Slog.e(TAG, "Can't track mobile network " + network + ": " + e.getMessage());
+ }
+ if (DBG) Slog.d(TAG, "Tracking mobile network " + network);
+ }
+
+ @Override
+ public void onLost(Network network) {
+ MultipathTracker existing = mMultipathTrackers.get(network);
+ if (existing != null) {
+ existing.shutdown();
+ mMultipathTrackers.remove(network);
+ }
+ if (DBG) Slog.d(TAG, "No longer tracking mobile network " + network);
+ }
+ };
+
+ mCM.registerNetworkCallback(request, mMobileNetworkCallback, mHandler);
+ }
+
+ private void maybeUnregisterTrackMobileCallback() {
+ if (mMobileNetworkCallback != null) {
+ mCM.unregisterNetworkCallback(mMobileNetworkCallback);
+ }
+ mMobileNetworkCallback = null;
+ }
+
+ private void registerNetworkPolicyListener() {
+ mPolicyListener = new NetworkPolicyManager.Listener() {
+ @Override
+ public void onMeteredIfacesChanged(String[] meteredIfaces) {
+ // Dispatched every time opportunistic quota is recalculated.
+ mHandler.post(() -> {
+ for (MultipathTracker t : mMultipathTrackers.values()) {
+ t.updateMultipathBudget();
+ }
+ });
+ }
+ };
+ mNPM.registerListener(mPolicyListener);
+ }
+
+ private void unregisterNetworkPolicyListener() {
+ mNPM.unregisterListener(mPolicyListener);
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ // Do not use in production. Access to class data is only safe on the handler thrad.
+ pw.println("MultipathPolicyTracker:");
+ pw.increaseIndent();
+ for (MultipathTracker t : mMultipathTrackers.values()) {
+ pw.println(String.format("Network %s: quota %d, budget %d. Preference: %s",
+ t.network, t.getQuota(), t.getMultipathBudget(),
+ DebugUtils.flagsToString(ConnectivityManager.class, "MULTIPATH_PREFERENCE_",
+ t.getMultipathPreference())));
+ }
+ pw.decreaseIndent();
+ }
+}
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
index 7cd3406..39fc019 100644
--- a/services/core/java/com/android/server/TextServicesManagerService.java
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -701,7 +701,8 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
- if (args.length == 0) { // Dump all users' data
+ if (args.length == 0 || (args.length == 1 && args[0].equals("-a"))) {
+ // Dump all users' data
synchronized (mLock) {
pw.println("Current Text Services Manager state:");
pw.println(" Users:");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 8397615..f469437 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -228,6 +228,7 @@
import android.app.BroadcastOptions;
import android.app.ContentProviderHolder;
import android.app.Dialog;
+import android.app.GrantedUriPermission;
import android.app.IActivityController;
import android.app.IActivityManager;
import android.app.IApplicationThread;
@@ -3463,6 +3464,7 @@
final void showAppWarningsIfNeededLocked(ActivityRecord r) {
mAppWarnings.showUnsupportedCompileSdkDialogIfNeeded(r);
mAppWarnings.showUnsupportedDisplaySizeDialogIfNeeded(r);
+ mAppWarnings.showDeprecatedTargetDialogIfNeeded(r);
}
private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
@@ -4060,10 +4062,15 @@
if (app.info.isPrivilegedApp() &&
SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false)) {
- runtimeFlags |= Zygote.DISABLE_VERIFIER;
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
}
+ if (app.info.isAllowedToUseHiddenApi()) {
+ // This app is allowed to use undocumented and private APIs. Set
+ // up its runtime with the appropriate flag.
+ runtimeFlags |= Zygote.DISABLE_HIDDEN_API_CHECKS;
+ }
+
String invokeWith = null;
if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
// Debuggable apps may include a wrapper script with their library directory.
@@ -10198,7 +10205,8 @@
if (perms == null) {
Slog.w(TAG, "No permission grants found for " + packageName);
} else {
- for (UriPermission perm : perms.values()) {
+ for (int j = 0; j < perms.size(); j++) {
+ final UriPermission perm = perms.valueAt(j);
if (packageName.equals(perm.targetPkg) && perm.persistedModeFlags != 0) {
result.add(perm.buildPersistedPublicApiObject());
}
@@ -10209,7 +10217,8 @@
for (int i = 0; i < size; i++) {
final ArrayMap<GrantUri, UriPermission> perms =
mGrantedUriPermissions.valueAt(i);
- for (UriPermission perm : perms.values()) {
+ for (int j = 0; j < perms.size(); j++) {
+ final UriPermission perm = perms.valueAt(j);
if (packageName.equals(perm.sourcePkg) && perm.persistedModeFlags != 0) {
result.add(perm.buildPersistedPublicApiObject());
}
@@ -10221,25 +10230,27 @@
}
@Override
- public ParceledListSlice<android.content.UriPermission> getGrantedUriPermissions(
- String packageName, int userId) {
+ public ParceledListSlice<GrantedUriPermission> getGrantedUriPermissions(
+ @Nullable String packageName, int userId) {
enforceCallingPermission(android.Manifest.permission.GET_APP_GRANTED_URI_PERMISSIONS,
"getGrantedUriPermissions");
- final ArrayList<android.content.UriPermission> result = Lists.newArrayList();
+ final List<GrantedUriPermission> result = new ArrayList<>();
synchronized (this) {
final int size = mGrantedUriPermissions.size();
for (int i = 0; i < size; i++) {
final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
- for (UriPermission perm : perms.values()) {
- if (packageName.equals(perm.targetPkg) && perm.targetUserId == userId
+ for (int j = 0; j < perms.size(); j++) {
+ final UriPermission perm = perms.valueAt(j);
+ if ((packageName == null || packageName.equals(perm.targetPkg))
+ && perm.targetUserId == userId
&& perm.persistedModeFlags != 0) {
- result.add(perm.buildPersistedPublicApiObject());
+ result.add(perm.buildGrantedUriPermission());
}
}
}
}
- return new ParceledListSlice<android.content.UriPermission>(result);
+ return new ParceledListSlice<>(result);
}
@Override
@@ -13268,6 +13279,9 @@
case ActivityManager.BUGREPORT_OPTION_TELEPHONY:
extraOptions = "bugreporttelephony";
break;
+ case ActivityManager.BUGREPORT_OPTION_WIFI:
+ extraOptions = "bugreportwifi";
+ break;
default:
throw new IllegalArgumentException("Provided bugreport type is not correct, value: "
+ bugreportType);
@@ -13289,9 +13303,8 @@
* No new code should be calling it.
*/
@Deprecated
- @Override
- public void requestTelephonyBugReport(String shareTitle, String shareDescription) {
-
+ private void requestBugReportWithDescription(String shareTitle, String shareDescription,
+ int bugreportType) {
if (!TextUtils.isEmpty(shareTitle)) {
if (shareTitle.length() > MAX_BUGREPORT_TITLE_SIZE) {
String errorStr = "shareTitle should be less than " +
@@ -13320,9 +13333,34 @@
Slog.d(TAG, "Bugreport notification title " + shareTitle
+ " description " + shareDescription);
- requestBugReport(ActivityManager.BUGREPORT_OPTION_TELEPHONY);
+ requestBugReport(bugreportType);
}
+ /**
+ * @deprecated This method is only used by a few internal components and it will soon be
+ * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+ * No new code should be calling it.
+ */
+ @Deprecated
+ @Override
+ public void requestTelephonyBugReport(String shareTitle, String shareDescription) {
+ requestBugReportWithDescription(shareTitle, shareDescription,
+ ActivityManager.BUGREPORT_OPTION_TELEPHONY);
+ }
+
+ /**
+ * @deprecated This method is only used by a few internal components and it will soon be
+ * replaced by a proper bug report API (which will be restricted to a few, pre-defined apps).
+ * No new code should be calling it.
+ */
+ @Deprecated
+ @Override
+ public void requestWifiBugReport(String shareTitle, String shareDescription) {
+ requestBugReportWithDescription(shareTitle, shareDescription,
+ ActivityManager.BUGREPORT_OPTION_WIFI);
+ }
+
+
public static long getInputDispatchingTimeoutLocked(ActivityRecord r) {
return r != null ? getInputDispatchingTimeoutLocked(r.app) : KEY_DISPATCHING_TIMEOUT;
}
@@ -13746,7 +13784,7 @@
int index = task.mActivities.lastIndexOf(r);
if (index > 0) {
ActivityRecord under = task.mActivities.get(index - 1);
- under.returningOptions = safeOptions.getOptions(r);
+ under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
}
final boolean translucentChanged = r.changeWindowTranslucency(false);
if (translucentChanged) {
diff --git a/services/core/java/com/android/server/am/AppWarnings.java b/services/core/java/com/android/server/am/AppWarnings.java
index a3c0345..806e95d 100644
--- a/services/core/java/com/android/server/am/AppWarnings.java
+++ b/services/core/java/com/android/server/am/AppWarnings.java
@@ -50,6 +50,7 @@
public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01;
public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
+ public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
@@ -61,6 +62,7 @@
private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog;
+ private DeprecatedTargetSdkVersionDialog mDeprecatedTargetSdkVersionDialog;
/**
* Creates a new warning dialog manager.
@@ -126,6 +128,17 @@
}
/**
+ * Shows the "deprecated target sdk" warning, if necessary.
+ *
+ * @param r activity record for which the warning may be displayed
+ */
+ public void showDeprecatedTargetDialogIfNeeded(ActivityRecord r) {
+ if (r.appInfo.targetSdkVersion < Build.VERSION.MIN_SUPPORTED_TARGET_SDK_INT) {
+ mUiHandler.showDeprecatedTargetDialog(r);
+ }
+ }
+
+ /**
* Called when an activity is being started.
*
* @param r record for the activity being started
@@ -133,6 +146,7 @@
public void onStartActivity(ActivityRecord r) {
showUnsupportedCompileSdkDialogIfNeeded(r);
showUnsupportedDisplaySizeDialogIfNeeded(r);
+ showDeprecatedTargetDialogIfNeeded(r);
}
/**
@@ -237,6 +251,27 @@
}
/**
+ * Shows the "deprecated target sdk version" warning for the given application.
+ * <p>
+ * <strong>Note:</strong> Must be called on the UI thread.
+ *
+ * @param ar record for the activity that triggered the warning
+ */
+ @UiThread
+ private void showDeprecatedTargetSdkDialogUiThread(ActivityRecord ar) {
+ if (mDeprecatedTargetSdkVersionDialog != null) {
+ mDeprecatedTargetSdkVersionDialog.dismiss();
+ mDeprecatedTargetSdkVersionDialog = null;
+ }
+ if (ar != null && !hasPackageFlag(
+ ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) {
+ mDeprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog(
+ AppWarnings.this, mUiContext, ar.info.applicationInfo);
+ mDeprecatedTargetSdkVersionDialog.show();
+ }
+ }
+
+ /**
* Dismisses all warnings for the given package.
* <p>
* <strong>Note:</strong> Must be called on the UI thread.
@@ -259,6 +294,13 @@
mUnsupportedCompileSdkDialog.dismiss();
mUnsupportedCompileSdkDialog = null;
}
+
+ // Hides the "deprecated target sdk version" dialog if necessary.
+ if (mDeprecatedTargetSdkVersionDialog != null && (name == null || name.equals(
+ mDeprecatedTargetSdkVersionDialog.getPackageName()))) {
+ mDeprecatedTargetSdkVersionDialog.dismiss();
+ mDeprecatedTargetSdkVersionDialog = null;
+ }
}
/**
@@ -282,7 +324,7 @@
void setPackageFlag(String name, int flag, boolean enabled) {
synchronized (mPackageFlags) {
final int curFlags = getPackageFlags(name);
- final int newFlags = enabled ? (curFlags & ~flag) : (curFlags | flag);
+ final int newFlags = enabled ? (curFlags | flag) : (curFlags & ~flag);
if (curFlags != newFlags) {
if (newFlags != 0) {
mPackageFlags.put(name, newFlags);
@@ -311,6 +353,7 @@
private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2;
private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3;
private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
+ private static final int MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG = 5;
public UiHandler(Looper looper) {
super(looper, null, true);
@@ -334,6 +377,10 @@
final String name = (String) msg.obj;
hideDialogsForPackageUiThread(name);
} break;
+ case MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG: {
+ final ActivityRecord ar = (ActivityRecord) msg.obj;
+ showDeprecatedTargetSdkDialogUiThread(ar);
+ } break;
}
}
@@ -352,6 +399,11 @@
obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget();
}
+ public void showDeprecatedTargetDialog(ActivityRecord r) {
+ removeMessages(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG);
+ obtainMessage(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG, r).sendToTarget();
+ }
+
public void hideDialogsForPackage(String name) {
obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget();
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 04b49ba..6b380f1 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -582,17 +582,11 @@
}
}
- public void noteStartGps(int uid) {
+ @Override
+ public void noteGpsChanged(WorkSource oldWs, WorkSource newWs) {
enforceCallingPermission();
synchronized (mStats) {
- mStats.noteStartGpsLocked(uid);
- }
- }
-
- public void noteStopGps(int uid) {
- enforceCallingPermission();
- synchronized (mStats) {
- mStats.noteStopGpsLocked(uid);
+ mStats.noteGpsChangedLocked(oldWs, newWs);
}
}
diff --git a/services/core/java/com/android/server/am/DeprecatedTargetSdkVersionDialog.java b/services/core/java/com/android/server/am/DeprecatedTargetSdkVersionDialog.java
new file mode 100644
index 0000000..84dca7f
--- /dev/null
+++ b/services/core/java/com/android/server/am/DeprecatedTargetSdkVersionDialog.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.SystemPropertiesProto;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+
+import com.android.internal.R;
+import com.android.server.utils.AppInstallerUtil;
+
+public class DeprecatedTargetSdkVersionDialog {
+ private final AlertDialog mDialog;
+ private final String mPackageName;
+
+ public DeprecatedTargetSdkVersionDialog(final AppWarnings manager, Context context,
+ ApplicationInfo appInfo) {
+ mPackageName = appInfo.packageName;
+
+ final PackageManager pm = context.getPackageManager();
+ final CharSequence label = appInfo.loadSafeLabel(pm);
+ final CharSequence message = context.getString(R.string.deprecated_target_sdk_message);
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setPositiveButton(R.string.ok, (dialog, which) ->
+ manager.setPackageFlag(
+ mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_SDK, true))
+ .setMessage(message)
+ .setTitle(label);
+
+ // If we might be able to update the app, show a button.
+ final Intent installerIntent = AppInstallerUtil.createIntent(context, appInfo.packageName);
+ if (installerIntent != null) {
+ builder.setNeutralButton(R.string.deprecated_target_sdk_app_store,
+ (dialog, which) -> {
+ context.startActivity(installerIntent);
+ });
+ }
+
+ // Ensure the content view is prepared.
+ mDialog = builder.create();
+ mDialog.create();
+
+ final Window window = mDialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+
+ // DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
+ window.getAttributes().setTitle("DeprecatedTargetSdkVersionDialog");
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public void show() {
+ mDialog.show();
+ }
+
+ public void dismiss() {
+ mDialog.dismiss();
+ }
+}
diff --git a/services/core/java/com/android/server/am/UriPermission.java b/services/core/java/com/android/server/am/UriPermission.java
index 90577e3..1e071aa 100644
--- a/services/core/java/com/android/server/am/UriPermission.java
+++ b/services/core/java/com/android/server/am/UriPermission.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.app.GrantedUriPermission;
import android.content.Intent;
import android.os.Binder;
import android.os.UserHandle;
@@ -387,4 +388,8 @@
public android.content.UriPermission buildPersistedPublicApiObject() {
return new android.content.UriPermission(uri.uri, persistedModeFlags, persistedCreateTime);
}
+
+ public GrantedUriPermission buildGrantedUriPermission() {
+ return new GrantedUriPermission(uri.uri, targetPkg);
+ }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
index f9b35f5..e77cb7a 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java
@@ -48,7 +48,7 @@
private boolean mIsClosed = false;
private boolean mIsMuted = false;
- private int mRegion; // TODO(b/62710330): find better solution to handle regions
+ private int mRegion;
private final boolean mWithAudio;
Tuner(@NonNull ITunerCallback clientCallback, int halRev,
@@ -89,7 +89,6 @@
private native void nativeCancelAnnouncement(long nativeContext);
- private native RadioManager.ProgramInfo nativeGetProgramInformation(long nativeContext);
private native boolean nativeStartBackgroundScan(long nativeContext);
private native List<RadioManager.ProgramInfo> nativeGetProgramList(long nativeContext,
Map<String, String> vendorFilter);
@@ -103,8 +102,6 @@
Map<String, String> parameters);
private native Map<String, String> nativeGetParameters(long nativeContext, List<String> keys);
- private native boolean nativeIsAntennaConnected(long nativeContext);
-
@Override
public void close() {
synchronized (mLock) {
@@ -218,14 +215,6 @@
}
@Override
- public RadioManager.ProgramInfo getProgramInformation() {
- synchronized (mLock) {
- checkNotClosedLocked();
- return nativeGetProgramInformation(mNativeContext);
- }
- }
-
- @Override
public Bitmap getImage(int id) {
if (id == 0) {
throw new IllegalArgumentException("Image ID is missing");
@@ -324,12 +313,4 @@
if (results == null) return Collections.emptyMap();
return results;
}
-
- @Override
- public boolean isAntennaConnected() {
- synchronized (mLock) {
- checkNotClosedLocked();
- return nativeIsAntennaConnected(mNativeContext);
- }
- }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
index 18f56ed..04c0e57 100644
--- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java
@@ -20,6 +20,7 @@
import android.hardware.radio.ITuner;
import android.hardware.radio.ITunerCallback;
import android.hardware.radio.ProgramList;
+import android.hardware.radio.ProgramSelector;
import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioMetadata;
import android.hardware.radio.RadioTuner;
@@ -100,6 +101,11 @@
}
@Override
+ public void onTuneFailed(int result, ProgramSelector selector) {
+ Slog.e(TAG, "Not applicable for HAL 1.x");
+ }
+
+ @Override
public void onConfigurationChanged(RadioManager.BandConfig config) {
dispatch(() -> mClientCallback.onConfigurationChanged(config));
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
index 7a95971..3bb3d1f 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java
@@ -73,7 +73,26 @@
}
}
- private static @NonNull Map<String, String>
+ static @NonNull ArrayList<VendorKeyValue>
+ vendorInfoToHal(@Nullable Map<String, String> info) {
+ if (info == null) return new ArrayList<>();
+
+ ArrayList<VendorKeyValue> list = new ArrayList<>();
+ for (Map.Entry<String, String> entry : info.entrySet()) {
+ VendorKeyValue elem = new VendorKeyValue();
+ elem.key = entry.getKey();
+ elem.value = entry.getValue();
+ if (elem.key == null || elem.value == null) {
+ Slog.w(TAG, "VendorKeyValue contains null pointers");
+ continue;
+ }
+ list.add(elem);
+ }
+
+ return list;
+ }
+
+ static @NonNull Map<String, String>
vendorInfoFromHal(@Nullable List<VendorKeyValue> info) {
if (info == null) return Collections.emptyMap();
@@ -206,18 +225,23 @@
false, // isCaptureSupported
amfmConfigToBands(amfmConfig),
- false, // isBgScanSupported is deprecated
+ true, // isBgScanSupported is deprecated
supportedProgramTypes,
supportedIdentifierTypes,
vendorInfoFromHal(prop.vendorInfo)
);
}
+ static void programIdentifierToHal(@NonNull ProgramIdentifier hwId,
+ @NonNull ProgramSelector.Identifier id) {
+ hwId.type = id.getType();
+ hwId.value = id.getValue();
+ }
+
static @NonNull ProgramIdentifier programIdentifierToHal(
@NonNull ProgramSelector.Identifier id) {
ProgramIdentifier hwId = new ProgramIdentifier();
- hwId.type = id.getType();
- hwId.value = id.getValue();
+ programIdentifierToHal(hwId, id);
return hwId;
}
@@ -227,10 +251,22 @@
return new ProgramSelector.Identifier(id.type, id.value);
}
+ static @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector programSelectorToHal(
+ @NonNull ProgramSelector sel) {
+ android.hardware.broadcastradio.V2_0.ProgramSelector hwSel =
+ new android.hardware.broadcastradio.V2_0.ProgramSelector();
+
+ programIdentifierToHal(hwSel.primaryId, sel.getPrimaryId());
+ Arrays.stream(sel.getSecondaryIds()).map(Convert::programIdentifierToHal).
+ forEachOrdered(hwSel.secondaryIds::add);
+
+ return hwSel;
+ }
+
static @NonNull ProgramSelector programSelectorFromHal(
@NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) {
ProgramSelector.Identifier[] secondaryIds = sel.secondaryIds.stream().
- map(id -> Objects.requireNonNull(programIdentifierFromHal(id))).
+ map(Convert::programIdentifierFromHal).map(Objects::requireNonNull).
toArray(ProgramSelector.Identifier[]::new);
return new ProgramSelector(
@@ -286,4 +322,10 @@
vendorInfoFromHal(hwAnnouncement.vendorInfo)
);
}
+
+ static <T> @Nullable ArrayList<T> listToArrayList(@Nullable List<T> list) {
+ if (list == null) return null;
+ if (list instanceof ArrayList) return (ArrayList) list;
+ return new ArrayList<>(list);
+ }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 4dff9e0..50f032d 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.hardware.radio.ITuner;
import android.hardware.radio.RadioManager;
import android.hardware.broadcastradio.V2_0.AmFmRegionConfig;
@@ -76,15 +78,17 @@
Mutable<ITunerSession> hwSession = new Mutable<>();
MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
- mService.openSession(cb, (int result, ITunerSession session) -> {
- hwSession.value = session;
- halResult.value = result;
- });
+ synchronized (mService) {
+ mService.openSession(cb, (result, session) -> {
+ hwSession.value = session;
+ halResult.value = result;
+ });
+ }
Convert.throwOnError("openSession", halResult.value);
Objects.requireNonNull(hwSession.value);
- return new TunerSession(hwSession.value, cb);
+ return new TunerSession(this, hwSession.value, cb);
}
public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
@@ -103,10 +107,13 @@
map(a -> Convert.announcementFromHal(a)).collect(Collectors.toList()));
}
};
- mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHandle) -> {
- halResult.value = result;
- hwCloseHandle.value = closeHandle;
- });
+
+ synchronized (mService) {
+ mService.registerAnnouncementListener(enabledList, hwListener, (result, closeHnd) -> {
+ halResult.value = result;
+ hwCloseHandle.value = closeHnd;
+ });
+ }
Convert.throwOnError("addAnnouncementListener", halResult.value);
return new android.hardware.radio.ICloseHandle.Stub() {
@@ -119,4 +126,21 @@
}
};
}
+
+ Bitmap getImage(int id) {
+ if (id == 0) throw new IllegalArgumentException("Image ID is missing");
+
+ byte[] rawImage;
+ synchronized (mService) {
+ List<Byte> rawList = Utils.maybeRethrow(() -> mService.getImage(id));
+ rawImage = new byte[rawList.size()];
+ for (int i = 0; i < rawList.size(); i++) {
+ rawImage[i] = rawList.get(i);
+ }
+ }
+
+ if (rawImage == null || rawImage.length == 0) return null;
+
+ return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
+ }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
index ed2a1b3..3c4b49c 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
@@ -50,10 +50,14 @@
}
@Override
- public void onTuneFailed(int result, ProgramSelector selector) {}
+ public void onTuneFailed(int result, ProgramSelector selector) {
+ dispatch(() -> mClientCb.onTuneFailed(result, Convert.programSelectorFromHal(selector)));
+ }
@Override
- public void onCurrentProgramInfoChanged(ProgramInfo info) {}
+ public void onCurrentProgramInfoChanged(ProgramInfo info) {
+ dispatch(() -> mClientCb.onCurrentProgramInfoChanged(Convert.programInfoFromHal(info)));
+ }
@Override
public void onProgramListUpdated(ProgramListChunk chunk) {
@@ -61,8 +65,12 @@
}
@Override
- public void onAntennaStateChange(boolean connected) {}
+ public void onAntennaStateChange(boolean connected) {
+ dispatch(() -> mClientCb.onAntennaState(connected));
+ }
@Override
- public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {}
+ public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {
+ dispatch(() -> mClientCb.onParametersUpdated(Convert.vendorInfoFromHal(parameters)));
+ }
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 1ae7d20..8efaa2a 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -41,6 +41,7 @@
private final Object mLock = new Object();
+ private final RadioModule mModule;
private final ITunerSession mHwSession;
private final TunerCallback mCallback;
private boolean mIsClosed = false;
@@ -50,7 +51,9 @@
// necessary only for older APIs compatibility
private RadioManager.BandConfig mDummyConfig = null;
- TunerSession(@NonNull ITunerSession hwSession, @NonNull TunerCallback callback) {
+ TunerSession(@NonNull RadioModule module, @NonNull ITunerSession hwSession,
+ @NonNull TunerCallback callback) {
+ mModule = Objects.requireNonNull(module);
mHwSession = Objects.requireNonNull(hwSession);
mCallback = Objects.requireNonNull(callback);
notifyAudioServiceLocked(true);
@@ -128,23 +131,29 @@
}
@Override
- public void step(boolean directionDown, boolean skipSubChannel) {
+ public void step(boolean directionDown, boolean skipSubChannel) throws RemoteException {
synchronized (mLock) {
checkNotClosedLocked();
+ int halResult = mHwSession.step(!directionDown);
+ Convert.throwOnError("step", halResult);
}
}
@Override
- public void scan(boolean directionDown, boolean skipSubChannel) {
+ public void scan(boolean directionDown, boolean skipSubChannel) throws RemoteException {
synchronized (mLock) {
checkNotClosedLocked();
+ int halResult = mHwSession.scan(!directionDown, skipSubChannel);
+ Convert.throwOnError("step", halResult);
}
}
@Override
- public void tune(ProgramSelector selector) {
+ public void tune(ProgramSelector selector) throws RemoteException {
synchronized (mLock) {
checkNotClosedLocked();
+ int halResult = mHwSession.tune(Convert.programSelectorToHal(selector));
+ Convert.throwOnError("tune", halResult);
}
}
@@ -152,36 +161,25 @@
public void cancel() {
synchronized (mLock) {
checkNotClosedLocked();
+ Utils.maybeRethrow(mHwSession::cancel);
}
}
@Override
public void cancelAnnouncement() {
- synchronized (mLock) {
- checkNotClosedLocked();
- }
- }
-
- @Override
- public RadioManager.ProgramInfo getProgramInformation() {
- synchronized (mLock) {
- checkNotClosedLocked();
- return null;
- }
+ Slog.i(TAG, "Announcements control doesn't involve cancelling at the HAL level in 2.x");
}
@Override
public Bitmap getImage(int id) {
- synchronized (mLock) {
- checkNotClosedLocked();
- return null;
- }
+ return mModule.getImage(id);
}
@Override
public boolean startBackgroundScan() {
Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.x");
- return false;
+ TunerCallback.dispatch(() -> mCallback.mClientCb.onBackgroundScanComplete());
+ return true;
}
@Override
@@ -240,7 +238,6 @@
Slog.v(TAG, "setConfigFlag " + ConfigFlag.toString(flag) + " = " + value);
synchronized (mLock) {
checkNotClosedLocked();
-
int halResult = mHwSession.setConfigFlag(flag, value);
Convert.throwOnError("setConfigFlag", halResult);
}
@@ -250,7 +247,8 @@
public Map setParameters(Map parameters) {
synchronized (mLock) {
checkNotClosedLocked();
- return null;
+ return Convert.vendorInfoFromHal(Utils.maybeRethrow(
+ () -> mHwSession.setParameters(Convert.vendorInfoToHal(parameters))));
}
}
@@ -258,15 +256,8 @@
public Map getParameters(List<String> keys) {
synchronized (mLock) {
checkNotClosedLocked();
- return null;
- }
- }
-
- @Override
- public boolean isAntennaConnected() {
- synchronized (mLock) {
- checkNotClosedLocked();
- return true;
+ return Convert.vendorInfoFromHal(Utils.maybeRethrow(
+ () -> mHwSession.getParameters(Convert.listToArrayList(keys))));
}
}
}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
index 3520f37..384c9ba 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/Utils.java
@@ -16,6 +16,9 @@
package com.android.server.broadcastradio.hal2;
+import android.annotation.NonNull;
+import android.os.RemoteException;
+
enum FrequencyBand {
UNKNOWN,
FM,
@@ -37,4 +40,29 @@
if (freq < 110000) return FrequencyBand.FM;
return FrequencyBand.UNKNOWN;
}
+
+ interface FuncThrowingRemoteException<T> {
+ T exec() throws RemoteException;
+ }
+
+ static <T> T maybeRethrow(@NonNull FuncThrowingRemoteException<T> r) {
+ try {
+ return r.exec();
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ return null; // unreachable
+ }
+ }
+
+ interface VoidFuncThrowingRemoteException {
+ void exec() throws RemoteException;
+ }
+
+ static void maybeRethrow(@NonNull VoidFuncThrowingRemoteException r) {
+ try {
+ r.exec();
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
deleted file mode 100644
index f6b73b7..0000000
--- a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity;
-
-import static android.net.util.NetworkConstants.IPV4_HEADER_MIN_LEN;
-import static android.net.util.NetworkConstants.UDP_HEADER_LEN;
-
-import android.system.OsConstants;
-import android.net.ConnectivityManager;
-import android.net.NetworkUtils;
-import android.net.util.IpUtils;
-
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-import static android.net.ConnectivityManager.PacketKeepalive.*;
-
-/**
- * Represents the actual packets that are sent by the
- * {@link android.net.ConnectivityManager.PacketKeepalive} API.
- *
- * @hide
- */
-public class KeepalivePacketData {
- /** Protocol of the packet to send; one of the OsConstants.ETH_P_* values. */
- public final int protocol;
-
- /** Source IP address */
- public final InetAddress srcAddress;
-
- /** Destination IP address */
- public final InetAddress dstAddress;
-
- /** Source port */
- public final int srcPort;
-
- /** Destination port */
- public final int dstPort;
-
- /** Destination MAC address. Can change if routing changes. */
- public byte[] dstMac;
-
- /** Packet data. A raw byte string of packet data, not including the link-layer header. */
- public final byte[] data;
-
- protected KeepalivePacketData(InetAddress srcAddress, int srcPort,
- InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException {
- this.srcAddress = srcAddress;
- this.dstAddress = dstAddress;
- this.srcPort = srcPort;
- this.dstPort = dstPort;
- this.data = data;
-
- // Check we have two IP addresses of the same family.
- if (srcAddress == null || dstAddress == null ||
- !srcAddress.getClass().getName().equals(dstAddress.getClass().getName())) {
- throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
- }
-
- // Set the protocol.
- if (this.dstAddress instanceof Inet4Address) {
- this.protocol = OsConstants.ETH_P_IP;
- } else if (this.dstAddress instanceof Inet6Address) {
- this.protocol = OsConstants.ETH_P_IPV6;
- } else {
- throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
- }
-
- // Check the ports.
- if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) {
- throw new InvalidPacketException(ERROR_INVALID_PORT);
- }
- }
-
- public static class InvalidPacketException extends Exception {
- final public int error;
- public InvalidPacketException(int error) {
- this.error = error;
- }
- }
-
- /**
- * Creates an IPsec NAT-T keepalive packet with the specified parameters.
- */
- public static KeepalivePacketData nattKeepalivePacket(
- InetAddress srcAddress, int srcPort,
- InetAddress dstAddress, int dstPort) throws InvalidPacketException {
-
- if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) {
- throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
- }
-
- if (dstPort != NATT_PORT) {
- throw new InvalidPacketException(ERROR_INVALID_PORT);
- }
-
- int length = IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN + 1;
- ByteBuffer buf = ByteBuffer.allocate(length);
- buf.order(ByteOrder.BIG_ENDIAN);
- buf.putShort((short) 0x4500); // IP version and TOS
- buf.putShort((short) length);
- buf.putInt(0); // ID, flags, offset
- buf.put((byte) 64); // TTL
- buf.put((byte) OsConstants.IPPROTO_UDP);
- int ipChecksumOffset = buf.position();
- buf.putShort((short) 0); // IP checksum
- buf.put(srcAddress.getAddress());
- buf.put(dstAddress.getAddress());
- buf.putShort((short) srcPort);
- buf.putShort((short) dstPort);
- buf.putShort((short) (length - 20)); // UDP length
- int udpChecksumOffset = buf.position();
- buf.putShort((short) 0); // UDP checksum
- buf.put((byte) 0xff); // NAT-T keepalive
- buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
- buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_MIN_LEN));
-
- return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
- }
-}
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 9e1f6b8..d24f9c9 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -18,10 +18,10 @@
import com.android.internal.util.HexDump;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.connectivity.KeepalivePacketData;
import com.android.server.connectivity.NetworkAgentInfo;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.PacketKeepalive;
+import android.net.KeepalivePacketData;
import android.net.LinkAddress;
import android.net.NetworkAgent;
import android.net.NetworkUtils;
@@ -129,7 +129,7 @@
.append("->")
.append(IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort))
.append(" interval=" + mInterval)
- .append(" data=" + HexDump.toHexString(mPacket.data))
+ .append(" packetData=" + HexDump.toHexString(mPacket.getPacket()))
.append(" uid=").append(mUid).append(" pid=").append(mPid)
.append(" ]")
.toString();
@@ -172,7 +172,7 @@
}
private int checkInterval() {
- return mInterval >= 20 ? SUCCESS : ERROR_INVALID_INTERVAL;
+ return mInterval >= 10 ? SUCCESS : ERROR_INVALID_INTERVAL;
}
private int isValid() {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index ff05723..be6c4a1 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -506,6 +506,7 @@
Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING);
intent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, type);
intent.putExtra(ConnectivityManager.EXTRA_PROVISION_CALLBACK, receiver);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final long ident = Binder.clearCallingIdentity();
try {
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 6c5bfc7..e445d27 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -25,6 +25,7 @@
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -57,8 +58,16 @@
// the user is satisfied with the result before storing the sample.
private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000;
+ // Timeout after which we remove the effects any user interactions might've had on the
+ // brightness mapping. This timeout doesn't start until we transition to a non-interactive
+ // display policy so that we don't reset while users are using their devices, but also so that
+ // we don't erroneously keep the short-term model if the device is dozing but the display is
+ // fully on.
+ private static final int SHORT_TERM_MODEL_TIMEOUT_MILLIS = 30000;
+
private static final int MSG_UPDATE_AMBIENT_LUX = 1;
private static final int MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE = 2;
+ private static final int MSG_RESET_SHORT_TERM_MODEL = 3;
// Length of the ambient light horizon used to calculate the long term estimate of ambient
// light.
@@ -173,8 +182,9 @@
// The last screen auto-brightness gamma. (For printing in dump() only.)
private float mLastScreenAutoBrightnessGamma = 1.0f;
- // Are we going to adjust brightness while dozing.
- private boolean mDozing;
+ // The current display policy. This is useful, for example, for knowing when we're dozing,
+ // where the light sensor may not be available.
+ private int mDisplayPolicy = DisplayPowerRequest.POLICY_OFF;
// True if we are collecting a brightness adjustment sample, along with some data
// for the initial state of the sample.
@@ -221,31 +231,72 @@
}
public int getAutomaticScreenBrightness() {
- if (mDozing) {
+ if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
return (int) (mScreenAutoBrightness * mDozeScaleFactor);
}
return mScreenAutoBrightness;
}
public void configure(boolean enable, @Nullable BrightnessConfiguration configuration,
- float adjustment, boolean dozing, boolean userInitiatedChange) {
+ float brightness, float adjustment, int displayPolicy, boolean userInitiatedChange) {
// While dozing, the application processor may be suspended which will prevent us from
// receiving new information from the light sensor. On some devices, we may be able to
// switch to a wake-up light sensor instead but for now we will simply disable the sensor
// and hold onto the last computed screen auto brightness. We save the dozing flag for
// debugging purposes.
- mDozing = dozing;
+ boolean dozing = (displayPolicy == DisplayPowerRequest.POLICY_DOZE);
boolean changed = setBrightnessConfiguration(configuration);
- changed |= setLightSensorEnabled(enable && !dozing);
- if (enable && !dozing && userInitiatedChange) {
+ changed |= setDisplayPolicy(displayPolicy);
+ if (userInitiatedChange && enable && !dozing) {
+ // Update the current brightness value.
+ changed |= setScreenBrightnessByUser(brightness);
prepareBrightnessAdjustmentSample();
}
changed |= setScreenAutoBrightnessAdjustment(adjustment);
+ changed |= setLightSensorEnabled(enable && !dozing);
if (changed) {
updateAutoBrightness(false /*sendUpdate*/);
}
}
+ private boolean setDisplayPolicy(int policy) {
+ if (mDisplayPolicy == policy) {
+ return false;
+ }
+ final int oldPolicy = mDisplayPolicy;
+ mDisplayPolicy = policy;
+ if (DEBUG) {
+ Slog.d(TAG, "Display policy transitioning from " + mDisplayPolicy + " to " + policy);
+ }
+ if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy)) {
+ mHandler.sendEmptyMessageDelayed(MSG_RESET_SHORT_TERM_MODEL,
+ SHORT_TERM_MODEL_TIMEOUT_MILLIS);
+ } else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) {
+ mHandler.removeMessages(MSG_RESET_SHORT_TERM_MODEL);
+ }
+ return true;
+ }
+
+ private static boolean isInteractivePolicy(int policy) {
+ return policy == DisplayPowerRequest.POLICY_BRIGHT
+ || policy == DisplayPowerRequest.POLICY_DIM
+ || policy == DisplayPowerRequest.POLICY_VR;
+ }
+
+ private boolean setScreenBrightnessByUser(float brightness) {
+ if (!mAmbientLuxValid) {
+ // If we don't have a valid ambient lux then we don't have a valid brightness anyways,
+ // and we can't use this data to add a new control point to the short-term model.
+ return false;
+ }
+ mBrightnessMapper.addUserDataPoint(mAmbientLux, brightness);
+ return true;
+ }
+
+ private void resetShortTermModel() {
+ mBrightnessMapper.clearUserDataPoints();
+ }
+
public boolean setBrightnessConfiguration(BrightnessConfiguration configuration) {
return mBrightnessMapper.setBrightnessConfiguration(configuration);
}
@@ -280,7 +331,7 @@
pw.println(" mScreenAutoBrightnessAdjustmentMaxGamma="
+ mScreenAutoBrightnessAdjustmentMaxGamma);
pw.println(" mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma);
- pw.println(" mDozing=" + mDozing);
+ pw.println(" mDisplayPolicy=" + mDisplayPolicy);
pw.println();
mBrightnessMapper.dump(pw);
@@ -364,6 +415,10 @@
if (DEBUG) {
Slog.d(TAG, "setAmbientLux(" + lux + ")");
}
+ if (lux < 0) {
+ Slog.w(TAG, "Ambient lux was negative, ignoring and setting to 0.");
+ lux = 0;
+ }
mAmbientLux = lux;
mBrighteningLuxThreshold = mDynamicHysteresis.getBrighteningThreshold(lux);
mDarkeningLuxThreshold = mDynamicHysteresis.getDarkeningThreshold(lux);
@@ -647,6 +702,10 @@
case MSG_BRIGHTNESS_ADJUSTMENT_SAMPLE:
collectBrightnessAdjustmentSample();
break;
+
+ case MSG_RESET_SHORT_TERM_MODEL:
+ resetShortTermModel();
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index ac0e1b5..436ebff 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -30,6 +30,7 @@
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import java.util.Arrays;
/**
* A utility to map from an ambient brightness to a display's "backlight" brightness based on the
@@ -42,6 +43,9 @@
private static final String TAG = "BrightnessMappingStrategy";
private static final boolean DEBUG = false;
+ private static final float LUX_GRAD_SMOOTHING = 0.25f;
+ private static final float MAX_GRAD = 1.0f;
+
@Nullable
public static BrightnessMappingStrategy create(Resources resources) {
float[] luxLevels = getLuxLevels(resources.getIntArray(
@@ -169,11 +173,28 @@
public abstract float getBrightness(float lux);
/**
- * Gets the display's brightness in nits for the given backlight value.
+ * Converts the provided backlight value to nits if possible.
*
* Returns -1.0f if there's no available mapping for the backlight to nits.
*/
- public abstract float getNits(int backlight);
+ public abstract float convertToNits(int backlight);
+
+ /**
+ * Adds a user interaction data point to the brightness mapping.
+ *
+ * Currently, we only keep track of one of these at a time to constrain what can happen to the
+ * curve.
+ */
+ public abstract void addUserDataPoint(float lux, float brightness);
+
+ /**
+ * Removes any short term adjustments made to the curve from user interactions.
+ *
+ * Note that this does *not* reset the mapping to its initial state, any brightness
+ * configurations that have been applied will continue to be in effect. This solely removes the
+ * effects of user interactions on the model.
+ */
+ public abstract void clearUserDataPoints();
public abstract void dump(PrintWriter pw);
@@ -183,6 +204,112 @@
return (float) brightness / PowerManager.BRIGHTNESS_ON;
}
+ private static Spline createSpline(float[] x, float[] y) {
+ Spline spline = Spline.createSpline(x, y);
+ if (DEBUG) {
+ Slog.d(TAG, "Spline: " + spline);
+ for (float v = 1f; v < x[x.length - 1] * 1.25f; v *= 1.25f) {
+ Slog.d(TAG, String.format(" %7.1f: %7.1f", v, spline.interpolate(v)));
+ }
+ }
+ return spline;
+ }
+
+ private static Pair<float[], float[]> insertControlPoint(
+ float[] luxLevels, float[] brightnessLevels, float lux, float brightness) {
+ if (DEBUG) {
+ Slog.d(TAG, "Inserting new control point at (" + lux + ", " + brightness + ")");
+ }
+ final int idx = findInsertionPoint(luxLevels, lux);
+ final float[] newLuxLevels;
+ final float[] newBrightnessLevels;
+ if (idx == luxLevels.length) {
+ newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1);
+ newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1);
+ newLuxLevels[idx] = lux;
+ newBrightnessLevels[idx] = brightness;
+ } else if (luxLevels[idx] == lux) {
+ newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length);
+ newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length);
+ newBrightnessLevels[idx] = brightness;
+ } else {
+ newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1);
+ System.arraycopy(newLuxLevels, idx, newLuxLevels, idx+1, luxLevels.length - idx);
+ newLuxLevels[idx] = lux;
+ newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1);
+ System.arraycopy(newBrightnessLevels, idx, newBrightnessLevels, idx+1,
+ brightnessLevels.length - idx);
+ newBrightnessLevels[idx] = brightness;
+ }
+ smoothCurve(newLuxLevels, newBrightnessLevels, idx);
+ return Pair.create(newLuxLevels, newBrightnessLevels);
+ }
+
+ /**
+ * Returns the index of the first value that's less than or equal to {@code val}.
+ *
+ * This assumes that {@code arr} is sorted. If all values in {@code arr} are greater
+ * than val, then it will return the length of arr as the insertion point.
+ */
+ private static int findInsertionPoint(float[] arr, float val) {
+ for (int i = 0; i < arr.length; i++) {
+ if (val <= arr[i]) {
+ return i;
+ }
+ }
+ return arr.length;
+ }
+
+ private static void smoothCurve(float[] lux, float[] brightness, int idx) {
+ if (DEBUG) {
+ Slog.d(TAG, "smoothCurve(lux=" + Arrays.toString(lux)
+ + ", brightness=" + Arrays.toString(brightness)
+ + ", idx=" + idx + ")");
+ }
+ float prevLux = lux[idx];
+ float prevBrightness = brightness[idx];
+ // Smooth curve for data points above the newly introduced point
+ for (int i = idx+1; i < lux.length; i++) {
+ float currLux = lux[i];
+ float currBrightness = brightness[i];
+ float maxBrightness = prevBrightness * permissibleRatio(currLux, prevLux);
+ float newBrightness = MathUtils.constrain(
+ currBrightness, prevBrightness, maxBrightness);
+ if (newBrightness == currBrightness) {
+ break;
+ }
+ prevLux = currLux;
+ prevBrightness = newBrightness;
+ brightness[i] = newBrightness;
+ }
+
+ // Smooth curve for data points below the newly introduced point
+ prevLux = lux[idx];
+ prevBrightness = brightness[idx];
+ for (int i = idx-1; i >= 0; i--) {
+ float currLux = lux[i];
+ float currBrightness = brightness[i];
+ float minBrightness = prevBrightness * permissibleRatio(currLux, prevLux);
+ float newBrightness = MathUtils.constrain(
+ currBrightness, minBrightness, prevBrightness);
+ if (newBrightness == currBrightness) {
+ break;
+ }
+ prevLux = currLux;
+ prevBrightness = newBrightness;
+ brightness[i] = newBrightness;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Smoothed Curve: lux=" + Arrays.toString(lux)
+ + ", brightness=" + Arrays.toString(brightness));
+ }
+ }
+
+ private static float permissibleRatio(float currLux, float prevLux) {
+ return MathUtils.exp(MAX_GRAD
+ * (MathUtils.log(currLux + LUX_GRAD_SMOOTHING)
+ - MathUtils.log(prevLux + LUX_GRAD_SMOOTHING)));
+ }
/**
* A {@link BrightnessMappingStrategy} that maps from ambient room brightness directly to the
@@ -192,7 +319,14 @@
* configurations that are set are just ignored.
*/
private static class SimpleMappingStrategy extends BrightnessMappingStrategy {
- private final Spline mSpline;
+ // Lux control points
+ private final float[] mLux;
+ // Brightness control points normalized to [0, 1]
+ private final float[] mBrightness;
+
+ private Spline mSpline;
+ private float mUserLux;
+ private float mUserBrightness;
public SimpleMappingStrategy(float[] lux, int[] brightness) {
Preconditions.checkArgument(lux.length != 0 && brightness.length != 0,
@@ -204,20 +338,16 @@
0, Integer.MAX_VALUE, "brightness");
final int N = brightness.length;
- float[] x = new float[N];
- float[] y = new float[N];
+ mLux = new float[N];
+ mBrightness = new float[N];
for (int i = 0; i < N; i++) {
- x[i] = lux[i];
- y[i] = normalizeAbsoluteBrightness(brightness[i]);
+ mLux[i] = lux[i];
+ mBrightness[i] = normalizeAbsoluteBrightness(brightness[i]);
}
- mSpline = Spline.createSpline(x, y);
- if (DEBUG) {
- Slog.d(TAG, "Auto-brightness spline: " + mSpline);
- for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) {
- Slog.d(TAG, String.format(" %7.1f: %7.1f", v, mSpline.interpolate(v)));
- }
- }
+ mSpline = createSpline(mLux, mBrightness);
+ mUserLux = -1;
+ mUserBrightness = -1;
}
@Override
@@ -231,14 +361,36 @@
}
@Override
- public float getNits(int backlight) {
+ public float convertToNits(int backlight) {
return -1.0f;
}
@Override
+ public void addUserDataPoint(float lux, float brightness) {
+ if (DEBUG){
+ Slog.d(TAG, "addUserDataPoint(lux=" + lux + ", brightness=" + brightness + ")");
+ }
+ Pair<float[], float[]> curve = insertControlPoint(mLux, mBrightness, lux, brightness);
+ mSpline = createSpline(curve.first, curve.second);
+ mUserLux = lux;
+ mUserBrightness = brightness;
+ }
+
+ @Override
+ public void clearUserDataPoints() {
+ if (mUserLux != -1) {
+ mSpline = createSpline(mLux, mBrightness);
+ mUserLux = -1;
+ mUserBrightness = -1;
+ }
+ }
+
+ @Override
public void dump(PrintWriter pw) {
pw.println("SimpleMappingStrategy");
pw.println(" mSpline=" + mSpline);
+ pw.println(" mUserLux=" + mUserLux);
+ pw.println(" mUserBrightness=" + mUserBrightness);
}
}
@@ -261,13 +413,16 @@
// [0, 1.0].
private final Spline mNitsToBacklightSpline;
- // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to
- // a brightness in nits.
- private final Spline mBacklightToNitsSpline;
-
// The default brightness configuration.
private final BrightnessConfiguration mDefaultConfig;
+ // A spline mapping from the device's backlight value, normalized to the range [0, 1.0], to
+ // a brightness in nits.
+ private Spline mBacklightToNitsSpline;
+
+ private float mUserLux;
+ private float mUserBrightness;
+
public PhysicalMappingStrategy(BrightnessConfiguration config,
float[] nits, int[] backlight) {
Preconditions.checkArgument(nits.length != 0 && backlight.length != 0,
@@ -279,6 +434,9 @@
Preconditions.checkArrayElementsInRange(backlight,
PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON, "backlight");
+ mUserLux = -1;
+ mUserBrightness = -1;
+
// Setup the backlight spline
final int N = nits.length;
float[] normalizedBacklight = new float[N];
@@ -286,15 +444,8 @@
normalizedBacklight[i] = normalizeAbsoluteBrightness(backlight[i]);
}
- mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight);
- mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits);
- if (DEBUG) {
- Slog.d(TAG, "Backlight spline: " + mNitsToBacklightSpline);
- for (float v = 1f; v < nits[nits.length - 1] * 1.25f; v *= 1.25f) {
- Slog.d(TAG, String.format(
- " %7.1f: %7.1f", v, mNitsToBacklightSpline.interpolate(v)));
- }
- }
+ mNitsToBacklightSpline = createSpline(nits, normalizedBacklight);
+ mBacklightToNitsSpline = createSpline(normalizedBacklight, nits);
mDefaultConfig = config;
setBrightnessConfiguration(config);
@@ -306,42 +457,59 @@
config = mDefaultConfig;
}
if (config.equals(mConfig)) {
- if (DEBUG) {
- Slog.d(TAG, "Tried to set an identical brightness config, ignoring");
- }
return false;
}
Pair<float[], float[]> curve = config.getCurve();
- mBrightnessSpline = Spline.createSpline(curve.first /*lux*/, curve.second /*nits*/);
- if (DEBUG) {
- Slog.d(TAG, "Brightness spline: " + mBrightnessSpline);
- final float[] lux = curve.first;
- for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) {
- Slog.d(TAG, String.format(
- " %7.1f: %7.1f", v, mBrightnessSpline.interpolate(v)));
- }
- }
+ mBrightnessSpline = createSpline(curve.first /*lux*/, curve.second /*nits*/);
mConfig = config;
return true;
}
@Override
public float getBrightness(float lux) {
- return mNitsToBacklightSpline.interpolate(mBrightnessSpline.interpolate(lux));
+ float nits = mBrightnessSpline.interpolate(lux);
+ float backlight = mNitsToBacklightSpline.interpolate(nits);
+ return backlight;
}
@Override
- public float getNits(int backlight) {
+ public float convertToNits(int backlight) {
return mBacklightToNitsSpline.interpolate(normalizeAbsoluteBrightness(backlight));
}
@Override
+ public void addUserDataPoint(float lux, float backlight) {
+ if (DEBUG){
+ Slog.d(TAG, "addUserDataPoint(lux=" + lux + ", backlight=" + backlight + ")");
+ }
+ float brightness = mBacklightToNitsSpline.interpolate(backlight);
+ Pair<float[], float[]> defaultCurve = mConfig.getCurve();
+ Pair<float[], float[]> newCurve =
+ insertControlPoint(defaultCurve.first, defaultCurve.second, lux, brightness);
+ mBrightnessSpline = createSpline(newCurve.first, newCurve.second);
+ mUserLux = lux;
+ mUserBrightness = brightness;
+ }
+
+ @Override
+ public void clearUserDataPoints() {
+ if (mUserLux != -1) {
+ Pair<float[], float[]> defaultCurve = mConfig.getCurve();
+ mBrightnessSpline = createSpline(defaultCurve.first, defaultCurve.second);
+ mUserLux = -1;
+ mUserBrightness = -1;
+ }
+ }
+
+ @Override
public void dump(PrintWriter pw) {
pw.println("PhysicalMappingStrategy");
pw.println(" mConfig=" + mConfig);
pw.println(" mBrightnessSpline=" + mBrightnessSpline);
pw.println(" mNitsToBacklightSpline=" + mNitsToBacklightSpline);
+ pw.println(" mUserLux=" + mUserLux);
+ pw.println(" mUserBrightness=" + mUserBrightness);
}
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index c77ec20..0c2ff05 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1857,6 +1857,36 @@
}
}
+ @Override // Binder call
+ public void setTemporaryBrightness(int brightness) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
+ "Permission required to set the display's brightness");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ mDisplayPowerController.setTemporaryBrightness(brightness);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override // Binder call
+ public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
+ "Permission required to set the display's auto brightness adjustment");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mSyncRoot) {
+ mDisplayPowerController.setTemporaryAutoBrightnessAdjustment(adjustment);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
private boolean validatePackageName(int uid, String packageName) {
if (packageName != null) {
String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index d2b8e5c..056c3e6 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -29,6 +29,7 @@
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
@@ -37,6 +38,7 @@
import android.hardware.display.BrightnessConfiguration;
import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -44,6 +46,8 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.MathUtils;
import android.util.Slog;
import android.util.Spline;
@@ -99,6 +103,8 @@
private static final int MSG_SCREEN_ON_UNBLOCKED = 3;
private static final int MSG_SCREEN_OFF_UNBLOCKED = 4;
private static final int MSG_CONFIGURE_BRIGHTNESS = 5;
+ private static final int MSG_SET_TEMPORARY_BRIGHTNESS = 6;
+ private static final int MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT = 7;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -144,6 +150,12 @@
// The display blanker.
private final DisplayBlanker mBlanker;
+ // Tracker for brightness changes.
+ private final BrightnessTracker mBrightnessTracker;
+
+ // Tracker for brightness settings changes.
+ private final SettingsObserver mSettingsObserver;
+
// The proximity sensor, or null if not available or needed.
private Sensor mProximitySensor;
@@ -159,6 +171,12 @@
// The maximum allowed brightness.
private final int mScreenBrightnessRangeMaximum;
+ // The default screen brightness.
+ private final int mScreenBrightnessDefault;
+
+ // The default screen brightness for VR.
+ private final int mScreenBrightnessForVrDefault;
+
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
@@ -298,20 +316,42 @@
// The last brightness that was set by the user and not temporary. Set to -1 when a brightness
// has yet to be recorded.
- private int mLastBrightness;
+ private int mLastUserSetScreenBrightness;
+
+ // The screen brightenss setting has changed but not taken effect yet. If this is different
+ // from the current screen brightness setting then this is coming from something other than us
+ // and should be considered a user interaction.
+ private int mPendingScreenBrightnessSetting;
+
+ // The last observed screen brightness setting, either set by us or by the settings app on
+ // behalf of the user.
+ private int mCurrentScreenBrightnessSetting;
+
+ // The temporary screen brightness. Typically set when a user is interacting with the
+ // brightness slider but hasn't settled on a choice yet. Set to -1 when there's no temporary
+ // brightness set.
+ private int mTemporaryScreenBrightness;
+
+ // The current screen brightness while in VR mode.
+ private int mScreenBrightnessForVr;
// The last auto brightness adjustment that was set by the user and not temporary. Set to
// Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
- private float mLastAutoBrightnessAdjustment;
+ private float mAutoBrightnessAdjustment;
+
+ // The pending auto brightness adjustment that will take effect on the next power state update.
+ private float mPendingAutoBrightnessAdjustment;
+
+ // The temporary auto brightness adjustment. Typically set when a user is interacting with the
+ // adjustment slider but hasn't settled on a choice yet. Set to Float.NaN when there's no
+ // temporary adjustment set.
+ private float mTemporaryAutoBrightnessAdjustment;
// Animators.
private ObjectAnimator mColorFadeOnAnimator;
private ObjectAnimator mColorFadeOffAnimator;
private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
- // Tracker for brightness changes
- private final BrightnessTracker mBrightnessTracker;
-
/**
* Creates the display power controller.
*/
@@ -320,6 +360,7 @@
SensorManager sensorManager, DisplayBlanker blanker) {
mHandler = new DisplayControllerHandler(handler.getLooper());
mBrightnessTracker = new BrightnessTracker(context, null);
+ mSettingsObserver = new SettingsObserver(mHandler);
mCallbacks = callbacks;
mBatteryStats = BatteryStatsService.getService();
@@ -343,6 +384,10 @@
mScreenBrightnessRangeMaximum = clampAbsoluteBrightness(resources.getInteger(
com.android.internal.R.integer.config_screenBrightnessSettingMaximum));
+ mScreenBrightnessDefault = clampAbsoluteBrightness(resources.getInteger(
+ com.android.internal.R.integer.config_screenBrightnessSettingDefault));
+ mScreenBrightnessForVrDefault = clampAbsoluteBrightness(resources.getInteger(
+ com.android.internal.R.integer.config_screenBrightnessForVrSettingDefault));
mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
com.android.internal.R.bool.config_automatic_brightness_available);
@@ -429,8 +474,11 @@
}
}
- mLastBrightness = -1;
- mLastAutoBrightnessAdjustment = Float.NaN;
+ mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+ mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
+ mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+ mTemporaryScreenBrightness = -1;
+ mTemporaryAutoBrightnessAdjustment = Float.NaN;
}
/**
@@ -553,10 +601,17 @@
}
// Initialize all of the brightness tracking state
- final float brightness = getNits(mPowerState.getScreenBrightness());
+ final float brightness = convertToNits(mPowerState.getScreenBrightness());
if (brightness >= 0.0f) {
mBrightnessTracker.start(brightness);
}
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS),
+ false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ),
+ false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
}
private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
@@ -586,7 +641,6 @@
// Update the power state request.
final boolean mustNotify;
boolean mustInitialize = false;
- boolean autoBrightnessAdjustmentChanged = false;
synchronized (mLock) {
mPendingUpdatePowerStateLocked = false;
@@ -601,8 +655,6 @@
mPendingRequestChangedLocked = false;
mustInitialize = true;
} else if (mPendingRequestChangedLocked) {
- autoBrightnessAdjustmentChanged = (mPowerRequest.screenAutoBrightnessAdjustment
- != mPendingRequestLocked.screenAutoBrightnessAdjustment);
mPowerRequest.copyFrom(mPendingRequestLocked);
mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
mPendingWaitForNegativeProximityLocked = false;
@@ -691,6 +743,14 @@
brightness = PowerManager.BRIGHTNESS_OFF;
}
+ // Always use the VR brightness when in the VR state.
+ if (state == Display.STATE_VR) {
+ brightness = mScreenBrightnessForVr;
+ }
+
+ if (brightness < 0 && mPowerRequest.screenBrightnessOverride > 0) {
+ brightness = mPowerRequest.screenBrightnessOverride;
+ }
final boolean autoBrightnessEnabledInDoze =
mAllowAutoBrightnessWhileDozingConfig && Display.isDozeState(state);
@@ -698,38 +758,54 @@
&& (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& brightness < 0
&& mAutomaticBrightnessController != null;
- final boolean brightnessAdjustmentChanged =
- !Float.isNaN(mLastAutoBrightnessAdjustment)
- && mPowerRequest.screenAutoBrightnessAdjustment != mLastAutoBrightnessAdjustment;
- final boolean brightnessChanged = mLastBrightness >= 0
- && mPowerRequest.screenBrightness != mLastBrightness;
+ boolean brightnessIsTemporary = false;
- // Update the last set brightness values.
- final boolean userInitiatedChange;
- if (mPowerRequest.brightnessSetByUser && !mPowerRequest.brightnessIsTemporary) {
- userInitiatedChange = autoBrightnessEnabled && brightnessAdjustmentChanged
- || !autoBrightnessEnabled && brightnessChanged;
- mLastBrightness = mPowerRequest.screenBrightness;
- mLastAutoBrightnessAdjustment = mPowerRequest.screenAutoBrightnessAdjustment;
- } else {
- userInitiatedChange = false;
+ final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
+ if (userSetBrightnessChanged) {
+ mTemporaryScreenBrightness = -1;
}
+ // Use the temporary screen brightness if there isn't an override, either from
+ // WindowManager or based on the display state.
+ if (mTemporaryScreenBrightness > 0) {
+ brightness = mTemporaryScreenBrightness;
+ brightnessIsTemporary = true;
+ }
+
+ final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
+ if (autoBrightnessAdjustmentChanged) {
+ mTemporaryAutoBrightnessAdjustment = Float.NaN;
+ }
+
+ // Use the autobrightness adjustment override if set.
+ final float autoBrightnessAdjustment;
+ if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) {
+ autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment;
+ brightnessIsTemporary = true;
+ } else {
+ autoBrightnessAdjustment = mAutoBrightnessAdjustment;
+ }
+
+ // Apply brightness boost.
+ // We do this here after deciding whether auto-brightness is enabled so that we don't
+ // disable the light sensor during this temporary state. That way when boost ends we will
+ // be able to resume normal auto-brightness behavior without any delay.
+ if (mPowerRequest.boostScreenBrightness
+ && brightness != PowerManager.BRIGHTNESS_OFF) {
+ brightness = PowerManager.BRIGHTNESS_ON;
+ }
+
+ // If the brightness is already set then it's been overriden by something other than the
+ // user, or is a temporary adjustment.
+ final boolean userInitiatedChange = brightness < 0
+ && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
+
// Configure auto-brightness.
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.configure(autoBrightnessEnabled,
- mBrightnessConfiguration, mPowerRequest.screenAutoBrightnessAdjustment,
- state != Display.STATE_ON, userInitiatedChange);
- }
-
- // Apply brightness boost.
- // We do this here after configuring auto-brightness so that we don't
- // disable the light sensor during this temporary state. That way when
- // boost ends we will be able to resume normal auto-brightness behavior
- // without any delay.
- if (mPowerRequest.boostScreenBrightness
- && brightness != PowerManager.BRIGHTNESS_OFF) {
- brightness = PowerManager.BRIGHTNESS_ON;
+ mBrightnessConfiguration,
+ mLastUserSetScreenBrightness / (float) PowerManager.BRIGHTNESS_ON,
+ autoBrightnessAdjustment, mPowerRequest.policy, userInitiatedChange);
}
// Apply auto-brightness.
@@ -744,6 +820,11 @@
if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
slowChange = true; // slowly adapt to auto-brightness
}
+ // Tell the rest of the system about the new brightness. Note that we do this
+ // before applying the low power or dim transformations so that the slider
+ // accurately represents the full possible range, even if they range changes what
+ // it means in absolute terms.
+ putScreenBrightnessSetting(brightness);
mAppliedAutoBrightness = true;
} else {
mAppliedAutoBrightness = false;
@@ -762,9 +843,10 @@
// provide a nominal default value for the case where auto-brightness
// is not ready yet.
if (brightness < 0) {
- brightness = clampScreenBrightness(mPowerRequest.screenBrightness);
+ brightness = clampScreenBrightness(mLastUserSetScreenBrightness);
}
+
// Apply dimming by at least some minimum amount when user activity
// timeout is about to expire.
if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
@@ -833,19 +915,17 @@
final boolean isDisplayContentVisible =
mColorFadeEnabled && mPowerState.getColorFadeLevel() == 1.0f;
if (initialRampSkip || hasBrightnessBuckets
- || wasOrWillBeInVr || !isDisplayContentVisible) {
+ || wasOrWillBeInVr || !isDisplayContentVisible || brightnessIsTemporary) {
animateScreenBrightness(brightness, 0);
} else {
animateScreenBrightness(brightness,
slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast);
}
- final float brightnessInNits = getNits(brightness);
- if (!mPowerRequest.brightnessIsTemporary && brightnessInNits >= 0.0f) {
- // We only want to track changes made by the user and on devices that can actually
- // map the display backlight values into a physical brightness unit.
- mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiatedChange);
+ if (!brightnessIsTemporary) {
+ notifyBrightnessChanged(brightness, userInitiatedChange);
}
+
}
// Determine whether the display is ready for use in the newly requested state.
@@ -913,6 +993,18 @@
msg.sendToTarget();
}
+ public void setTemporaryBrightness(int brightness) {
+ Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_BRIGHTNESS,
+ brightness, 0 /*unused*/);
+ msg.sendToTarget();
+ }
+
+ public void setTemporaryAutoBrightnessAdjustment(float adjustment) {
+ Message msg = mHandler.obtainMessage(MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT,
+ Float.floatToIntBits(adjustment), 0 /*unused*/);
+ msg.sendToTarget();
+ }
+
private void blockScreenOn() {
if (mPendingScreenOnUnblocker == null) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, SCREEN_ON_BLOCKED_TRACE_NAME, 0);
@@ -1304,9 +1396,79 @@
mHandler.post(mOnStateChangedRunnable);
}
- private float getNits(int backlight) {
+ private void handleSettingsChange() {
+ mPendingScreenBrightnessSetting = getScreenBrightnessSetting();
+ mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+ // We don't bother with a pending variable for VR screen brightness since we just
+ // immediately adapt to it.
+ mScreenBrightnessForVr = getScreenBrightnessForVrSetting();
+ sendUpdatePowerState();
+ }
+
+ private float getAutoBrightnessAdjustmentSetting() {
+ final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+ return Float.isNaN(adj) ? 0.0f : clampAutoBrightnessAdjustment(adj);
+ }
+
+ private int getScreenBrightnessSetting() {
+ final int brightness = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, mScreenBrightnessDefault,
+ UserHandle.USER_CURRENT);
+ return clampAbsoluteBrightness(brightness);
+ }
+
+ private int getScreenBrightnessForVrSetting() {
+ final int brightness = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_FOR_VR, mScreenBrightnessForVrDefault,
+ UserHandle.USER_CURRENT);
+ return clampAbsoluteBrightness(brightness);
+ }
+
+ private void putScreenBrightnessSetting(int brightness) {
+ mCurrentScreenBrightnessSetting = brightness;
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, brightness,
+ UserHandle.USER_CURRENT);
+ }
+
+ private boolean updateAutoBrightnessAdjustment() {
+ if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
+ return false;
+ }
+ if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
+ return false;
+ }
+ mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
+ mPendingAutoBrightnessAdjustment = Float.NaN;
+ return true;
+ }
+
+ private boolean updateUserSetScreenBrightness() {
+ if (mPendingScreenBrightnessSetting < 0) {
+ return false;
+ }
+ if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
+ return false;
+ }
+ mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
+ mPendingScreenBrightnessSetting = -1;
+ return true;
+ }
+
+ private void notifyBrightnessChanged(int brightness, boolean userInitiated) {
+ final float brightnessInNits = convertToNits(brightness);
+ if (brightnessInNits >= 0.0f) {
+ // We only want to track changes on devices that can actually map the display backlight
+ // values into a physical brightness unit since the value provided by the API is in
+ // nits and not using the arbitrary backlight units.
+ mBrightnessTracker.notifyBrightnessChanged(brightnessInNits, userInitiated);
+ }
+ }
+
+ private float convertToNits(int backlight) {
if (mBrightnessMapper != null) {
- return mBrightnessMapper.getNits(backlight);
+ return mBrightnessMapper.convertToNits(backlight);
} else {
return -1.0f;
}
@@ -1390,8 +1552,11 @@
pw.println(" mPendingProximityDebounceTime="
+ TimeUtils.formatUptime(mPendingProximityDebounceTime));
pw.println(" mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
- pw.println(" mLastBrightness=" + mLastBrightness);
- pw.println(" mLastAutoBrightnessAdjustment=" + mLastAutoBrightnessAdjustment);
+ pw.println(" mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness);
+ pw.println(" mCurrentScreenBrightnessSetting=" + mCurrentScreenBrightnessSetting);
+ pw.println(" mPendingScreenBrightnessSetting=" + mPendingScreenBrightnessSetting);
+ pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
+ pw.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
pw.println(" mAppliedDimming=" + mAppliedDimming);
pw.println(" mAppliedLowPower=" + mAppliedLowPower);
@@ -1456,6 +1621,10 @@
return MathUtils.constrain(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON);
}
+ private static float clampAutoBrightnessAdjustment(float value) {
+ return MathUtils.constrain(value, -1.0f, 1.0f);
+ }
+
private final class DisplayControllerHandler extends Handler {
public DisplayControllerHandler(Looper looper) {
super(looper, null, true /*async*/);
@@ -1488,6 +1657,17 @@
mBrightnessConfiguration = (BrightnessConfiguration)msg.obj;
updatePowerState();
break;
+
+ case MSG_SET_TEMPORARY_BRIGHTNESS:
+ // TODO: Should we have a a timeout for the temporary brightness?
+ mTemporaryScreenBrightness = msg.arg1;
+ updatePowerState();
+ break;
+
+ case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT:
+ mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1);
+ updatePowerState();
+ break;
}
}
}
@@ -1509,6 +1689,18 @@
}
};
+
+ private final class SettingsObserver extends ContentObserver {
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ handleSettingsChange();
+ }
+ }
+
private final class ScreenOnUnblocker implements WindowManagerPolicy.ScreenOnListener {
@Override
public void onScreenOn() {
diff --git a/services/core/java/com/android/server/job/JobSchedulerInternal.java b/services/core/java/com/android/server/job/JobSchedulerInternal.java
index c97eeaf..4e74908 100644
--- a/services/core/java/com/android/server/job/JobSchedulerInternal.java
+++ b/services/core/java/com/android/server/job/JobSchedulerInternal.java
@@ -59,7 +59,6 @@
/**
* Stats about the first load after boot and the most recent save.
- * STOPSHIP Remove it and the relevant code once b/64536115 is fixed.
*/
public class JobStorePersistStats {
public int countAllJobsLoaded = -1;
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 83a3c19..f23147b 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -400,7 +400,7 @@
(PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
runningJob.getTag());
- wl.setWorkSource(new WorkSource(runningJob.getSourceUid()));
+ wl.setWorkSource(deriveWorkSource(runningJob));
wl.setReferenceCounted(false);
wl.acquire();
@@ -419,6 +419,19 @@
}
}
+ private WorkSource deriveWorkSource(JobStatus runningJob) {
+ final int jobUid = runningJob.getSourceUid();
+ if (WorkSource.isChainedBatteryAttributionEnabled(mContext)) {
+ WorkSource workSource = new WorkSource();
+ workSource.createWorkChain()
+ .addNode(jobUid, null)
+ .addNode(android.os.Process.SYSTEM_UID, "JobScheduler");
+ return workSource;
+ } else {
+ return new WorkSource(jobUid);
+ }
+ }
+
/** If the client service crashes we reschedule this job and clean up. */
@Override
public void onServiceDisconnected(ComponentName name) {
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index bbee0eb..a91f5a4 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -18,9 +18,11 @@
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.content.Context;
+import android.os.Process;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Slog;
@@ -52,6 +54,8 @@
private long mNextJobExpiredElapsedMillis;
private long mNextDelayExpiredElapsedMillis;
+ private final boolean mChainedAttributionEnabled;
+
private AlarmManager mAlarmService = null;
/** List of tracked jobs, sorted asc. by deadline */
private final List<JobStatus> mTrackedJobs = new LinkedList<>();
@@ -71,6 +75,7 @@
mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
+ mChainedAttributionEnabled = WorkSource.isChainedBatteryAttributionEnabled(context);
}
/**
@@ -113,7 +118,7 @@
maybeUpdateAlarmsLocked(
job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
- new WorkSource(job.getSourceUid(), job.getSourcePackageName()));
+ deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
}
}
@@ -179,9 +184,8 @@
break;
}
}
- setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryPackageName != null
- ? new WorkSource(nextExpiryUid, nextExpiryPackageName)
- : new WorkSource(nextExpiryUid));
+ setDeadlineExpiredAlarmLocked(nextExpiryTime,
+ deriveWorkSource(nextExpiryUid, nextExpiryPackageName));
}
}
@@ -236,9 +240,20 @@
if (ready) {
mStateChangedListener.onControllerStateChanged();
}
- setDelayExpiredAlarmLocked(nextDelayTime, nextDelayPackageName != null
- ? new WorkSource(nextDelayUid, nextDelayPackageName)
- : new WorkSource(nextDelayUid));
+ setDelayExpiredAlarmLocked(nextDelayTime,
+ deriveWorkSource(nextDelayUid, nextDelayPackageName));
+ }
+ }
+
+ private WorkSource deriveWorkSource(int uid, @Nullable String packageName) {
+ if (mChainedAttributionEnabled) {
+ WorkSource ws = new WorkSource();
+ ws.createWorkChain()
+ .addNode(uid, packageName)
+ .addNode(Process.SYSTEM_UID, "JobScheduler");
+ return ws;
+ } else {
+ return packageName == null ? new WorkSource(uid) : new WorkSource(uid, packageName);
}
}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 48d275c..55c0f5a 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -64,6 +64,7 @@
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
import android.provider.Settings;
import android.provider.Telephony.Carriers;
import android.provider.Telephony.Sms.Intents;
@@ -346,7 +347,8 @@
// Current request from underlying location clients.
private ProviderRequest mProviderRequest = null;
- // Current list of underlying location clients.
+ // The WorkSource associated with the most recent client request (i.e, most recent call to
+ // setRequest).
private WorkSource mWorkSource = null;
// True if gps should be disabled (used to support battery saver mode in settings).
private boolean mDisableGps = false;
@@ -408,6 +410,7 @@
private final IAppOpsService mAppOpsService;
private final IBatteryStats mBatteryStats;
+ // Current list of underlying location clients.
// only modified on handler thread
private WorkSource mClientSource = new WorkSource();
@@ -1345,46 +1348,80 @@
}
private void updateClientUids(WorkSource source) {
- // Update work source.
- WorkSource[] changes = mClientSource.setReturningDiffs(source);
- if (changes == null) {
+ if (source.equals(mClientSource)) {
return;
}
- WorkSource newWork = changes[0];
- WorkSource goneWork = changes[1];
- // Update sources that were not previously tracked.
- if (newWork != null) {
- int lastuid = -1;
- for (int i = 0; i < newWork.size(); i++) {
- try {
- int uid = newWork.get(i);
- mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
- AppOpsManager.OP_GPS, uid, newWork.getName(i));
- if (uid != lastuid) {
- lastuid = uid;
- mBatteryStats.noteStartGps(uid);
- }
- } catch (RemoteException e) {
- Log.w(TAG, "RemoteException", e);
- }
- }
+ // (1) Inform BatteryStats that the list of IDs we're tracking changed.
+ try {
+ mBatteryStats.noteGpsChanged(mClientSource, source);
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException", e);
}
- // Update sources that are no longer tracked.
- if (goneWork != null) {
- int lastuid = -1;
- for (int i = 0; i < goneWork.size(); i++) {
- try {
- int uid = goneWork.get(i);
- mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
- AppOpsManager.OP_GPS, uid, goneWork.getName(i));
- if (uid != lastuid) {
- lastuid = uid;
- mBatteryStats.noteStopGps(uid);
+ // (2) Inform AppOps service about the list of changes to UIDs.
+
+ List<WorkChain>[] diffs = WorkSource.diffChains(mClientSource, source);
+ if (diffs != null) {
+ List<WorkChain> newChains = diffs[0];
+ List<WorkChain> goneChains = diffs[1];
+
+ if (newChains != null) {
+ for (int i = 0; i < newChains.size(); ++i) {
+ final WorkChain newChain = newChains.get(i);
+ try {
+ mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+ AppOpsManager.OP_GPS, newChain.getAttributionUid(),
+ newChain.getAttributionTag());
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException", e);
}
- } catch (RemoteException e) {
- Log.w(TAG, "RemoteException", e);
+ }
+ }
+
+ if (goneChains != null) {
+ for (int i = 0; i < goneChains.size(); i++) {
+ final WorkChain goneChain = goneChains.get(i);
+ try {
+ mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
+ AppOpsManager.OP_GPS, goneChain.getAttributionUid(),
+ goneChain.getAttributionTag());
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException", e);
+ }
+ }
+ }
+
+ mClientSource.transferWorkChains(source);
+ }
+
+ // Update the flat UIDs and names list and inform app-ops of all changes.
+ WorkSource[] changes = mClientSource.setReturningDiffs(source);
+ if (changes != null) {
+ WorkSource newWork = changes[0];
+ WorkSource goneWork = changes[1];
+
+ // Update sources that were not previously tracked.
+ if (newWork != null) {
+ for (int i = 0; i < newWork.size(); i++) {
+ try {
+ mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+ AppOpsManager.OP_GPS, newWork.get(i), newWork.getName(i));
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException", e);
+ }
+ }
+ }
+
+ // Update sources that are no longer tracked.
+ if (goneWork != null) {
+ for (int i = 0; i < goneWork.size(); i++) {
+ try {
+ mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
+ AppOpsManager.OP_GPS, goneWork.get(i), goneWork.getName(i));
+ } catch (RemoteException e) {
+ Log.w(TAG, "RemoteException", e);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index b6c3c66..0d567d1 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -40,6 +40,7 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.HexDump;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -455,20 +456,64 @@
private byte[] decryptRecoveryKey(
RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
throws RemoteException, ServiceSpecificException {
+ // TODO: Remove the extensive loggings in this function
+ byte[] locallyEncryptedKey;
try {
- byte[] locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
+ locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
sessionEntry.getKeyClaimant(),
sessionEntry.getVaultParams(),
encryptedClaimResponse);
- return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
- } catch (InvalidKeyException | AEADBadTagException e) {
+ } catch (InvalidKeyException e) {
+ Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e);
+ Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()",
+ sessionEntry.getKeyClaimant()));
+ Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()",
+ sessionEntry.getVaultParams()));
+ Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse));
throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
"Failed to decrypt recovery key " + e.getMessage());
-
+ } catch (AEADBadTagException e) {
+ Log.e(TAG, "Got AEADBadTagException during decrypting recovery claim response", e);
+ Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()",
+ sessionEntry.getKeyClaimant()));
+ Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()",
+ sessionEntry.getVaultParams()));
+ Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse));
+ throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
+ "Failed to decrypt recovery key " + e.getMessage());
} catch (NoSuchAlgorithmException e) {
// Should never happen: all the algorithms used are required by AOSP implementations
throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
}
+
+ try {
+ return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
+ } catch (InvalidKeyException e) {
+ Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e);
+ Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()",
+ sessionEntry.getLskfHash()));
+ Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey));
+ throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
+ "Failed to decrypt recovery key " + e.getMessage());
+ } catch (AEADBadTagException e) {
+ Log.e(TAG, "Got AEADBadTagException during decrypting recovery key", e);
+ Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()",
+ sessionEntry.getLskfHash()));
+ Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey));
+ throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
+ "Failed to decrypt recovery key " + e.getMessage());
+ } catch (NoSuchAlgorithmException e) {
+ // Should never happen: all the algorithms used are required by AOSP implementations
+ throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+ }
+ }
+
+ private String constructLoggingMessage(String key, byte[] value) {
+ if (value == null) {
+ return key + " is null";
+ } else {
+ return key + ": " + HexDump.toHexString(value);
+ }
}
/**
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index c59c5f6..4000a11 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -23,7 +23,7 @@
import android.media.IMediaSession2;
import android.media.MediaController2;
import android.media.MediaSession2;
-import android.media.SessionToken;
+import android.media.SessionToken2;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
@@ -96,7 +96,7 @@
// TODO(jaewan): also add uid for multiuser support
@CallSuper
public @Nullable
- SessionToken createSessionToken(int sessionPid, String packageName, String id,
+ SessionToken2 createSessionToken(int sessionPid, String packageName, String id,
IMediaSession2 sessionBinder) {
if (mController != null) {
if (mSessionPid != sessionPid) {
@@ -130,20 +130,20 @@
*/
MediaController2 onCreateMediaController(
String packageName, String id, IMediaSession2 sessionBinder) {
- SessionToken token = new SessionToken(
- SessionToken.TYPE_SESSION, packageName, id, null, sessionBinder);
+ SessionToken2 token = new SessionToken2(
+ SessionToken2.TYPE_SESSION, packageName, id, null, sessionBinder);
return createMediaController(token);
}
- final MediaController2 createMediaController(SessionToken token) {
+ final MediaController2 createMediaController(SessionToken2 token) {
mControllerCallback = new ControllerCallback();
- return new MediaController2(mContext, token, mControllerCallback, mMainExecutor);
+ return new MediaController2(mContext, token, mMainExecutor, mControllerCallback);
}
/**
* @return controller. Note that framework can only call oneway calls.
*/
- public SessionToken getToken() {
+ public SessionToken2 getToken() {
return mController == null ? null : mController.getSessionToken();
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index c9c7d04..c7f6014 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -40,7 +40,7 @@
import android.media.IRemoteVolumeController;
import android.media.MediaLibraryService2;
import android.media.MediaSessionService2;
-import android.media.SessionToken;
+import android.media.SessionToken2;
import android.media.session.IActiveSessionsListener;
import android.media.session.ICallback;
import android.media.session.IOnMediaKeyListener;
@@ -481,7 +481,7 @@
+ serviceInfo.packageName + "/" + serviceInfo.name);
} else {
int type = (libraryServices.contains(services.get(i)))
- ? SessionToken.TYPE_LIBRARY_SERVICE : SessionToken.TYPE_SESSION_SERVICE;
+ ? SessionToken2.TYPE_LIBRARY_SERVICE : SessionToken2.TYPE_SESSION_SERVICE;
MediaSessionService2Record record =
new MediaSessionService2Record(getContext(), mSessionDestroyedListener,
type, serviceInfo.packageName, serviceInfo.name, id);
@@ -1416,7 +1416,7 @@
int pid = Binder.getCallingPid();
MediaSession2Record record;
- SessionToken token;
+ SessionToken2 token;
// TODO(jaewan): Add sanity check for the token if calling package is from uid.
synchronized (mLock) {
record = getSessionRecordLocked(sessionPackage, id);
@@ -1448,7 +1448,7 @@
boolean isActive = record.getSessionPid() != 0;
if ((!activeSessionOnly && isSessionService)
|| (!sessionServiceOnly && isActive)) {
- SessionToken token = record.getToken();
+ SessionToken2 token = record.getToken();
if (token != null) {
tokens.add(token.toBundle());
} else {
diff --git a/services/core/java/com/android/server/media/MediaSessionService2Record.java b/services/core/java/com/android/server/media/MediaSessionService2Record.java
index bd97dbc..d033f55 100644
--- a/services/core/java/com/android/server/media/MediaSessionService2Record.java
+++ b/services/core/java/com/android/server/media/MediaSessionService2Record.java
@@ -19,7 +19,7 @@
import android.content.Context;
import android.media.IMediaSession2;
import android.media.MediaController2;
-import android.media.SessionToken;
+import android.media.SessionToken2;
import android.media.MediaSessionService2;
/**
@@ -33,7 +33,7 @@
private final int mType;
private final String mServiceName;
- private final SessionToken mToken;
+ private final SessionToken2 mToken;
public MediaSessionService2Record(Context context,
SessionDestroyedListener sessionDestroyedListener, int type,
@@ -41,7 +41,7 @@
super(context, sessionDestroyedListener);
mType = type;
mServiceName = serviceName;
- mToken = new SessionToken(mType, packageName, id, mServiceName, null);
+ mToken = new SessionToken2(mType, packageName, id, mServiceName, null);
}
/**
@@ -51,7 +51,7 @@
@Override
MediaController2 onCreateMediaController(
String packageName, String id, IMediaSession2 sessionBinder) {
- SessionToken token = new SessionToken(mType, packageName, id, mServiceName, sessionBinder);
+ SessionToken2 token = new SessionToken2(mType, packageName, id, mServiceName, sessionBinder);
return createMediaController(token);
}
@@ -59,7 +59,7 @@
* @return token with no session binder information.
*/
@Override
- public SessionToken getToken() {
+ public SessionToken2 getToken() {
return mToken;
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 321af43..7600e81 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -283,7 +283,18 @@
}
void onOverlayPackageRemoved(@NonNull final String packageName, final int userId) {
- Slog.wtf(TAG, "onOverlayPackageRemoved called, but only pre-installed overlays supported");
+ try {
+ final OverlayInfo overlayInfo = mSettings.getOverlayInfo(packageName, userId);
+ if (mSettings.remove(packageName, userId)) {
+ removeIdmapIfPossible(overlayInfo);
+ if (overlayInfo.isEnabled()) {
+ // Only trigger updates if the overlay was enabled.
+ mListener.onOverlaysChanged(overlayInfo.targetPackageName, userId);
+ }
+ }
+ } catch (OverlayManagerSettings.BadKeyException e) {
+ Slog.e(TAG, "failed to remove overlay", e);
+ }
}
OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 7d00423..17b38de 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -104,7 +104,7 @@
return true;
}
- OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
+ @NonNull OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
throws BadKeyException {
final int idx = select(packageName, userId);
if (idx < 0) {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index cdc79c7..c3f20af 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -17,6 +17,7 @@
package com.android.server.pm;
import android.annotation.AppIdInt;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
@@ -35,6 +36,8 @@
import dalvik.system.VMRuntime;
+import java.io.FileDescriptor;
+
public class Installer extends SystemService {
private static final String TAG = "Installer";
@@ -286,43 +289,44 @@
public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
int dexoptNeeded, @Nullable String outputPath, int dexFlags,
String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries,
- @Nullable String seInfo, boolean downgrade, int targetSdkVersion)
- throws InstallerException {
+ @Nullable String seInfo, boolean downgrade, int targetSdkVersion,
+ @Nullable String profileName) throws InstallerException {
assertValidInstructionSet(instructionSet);
if (!checkBeforeRemote()) return;
try {
mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo, downgrade,
- targetSdkVersion);
+ targetSdkVersion, profileName);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
- public boolean mergeProfiles(int uid, String packageName) throws InstallerException {
- if (!checkBeforeRemote()) return false;
- try {
- return mInstalld.mergeProfiles(uid, packageName);
- } catch (Exception e) {
- throw InstallerException.from(e);
- }
- }
-
- public boolean dumpProfiles(int uid, String packageName, String codePaths)
+ public boolean mergeProfiles(int uid, String packageName, String profileName)
throws InstallerException {
if (!checkBeforeRemote()) return false;
try {
- return mInstalld.dumpProfiles(uid, packageName, codePaths);
+ return mInstalld.mergeProfiles(uid, packageName, profileName);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
- public boolean copySystemProfile(String systemProfile, int uid, String packageName)
+ public boolean dumpProfiles(int uid, String packageName, String profileName, String codePath)
throws InstallerException {
if (!checkBeforeRemote()) return false;
try {
- return mInstalld.copySystemProfile(systemProfile, uid, packageName);
+ return mInstalld.dumpProfiles(uid, packageName, profileName, codePath);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ public boolean copySystemProfile(String systemProfile, int uid, String packageName,
+ String profileName) throws InstallerException {
+ if (!checkBeforeRemote()) return false;
+ try {
+ return mInstalld.copySystemProfile(systemProfile, uid, packageName, profileName);
} catch (Exception e) {
throw InstallerException.from(e);
}
@@ -366,10 +370,10 @@
}
}
- public void clearAppProfiles(String packageName) throws InstallerException {
+ public void clearAppProfiles(String packageName, String profileName) throws InstallerException {
if (!checkBeforeRemote()) return;
try {
- mInstalld.clearAppProfiles(packageName);
+ mInstalld.clearAppProfiles(packageName, profileName);
} catch (Exception e) {
throw InstallerException.from(e);
}
@@ -478,6 +482,26 @@
}
}
+ public void installApkVerity(String filePath, FileDescriptor verityInput)
+ throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.installApkVerity(filePath, verityInput);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ public void assertFsverityRootHashMatches(String filePath, @NonNull byte[] expectedHash)
+ throws InstallerException {
+ if (!checkBeforeRemote()) return;
+ try {
+ mInstalld.assertFsverityRootHashMatches(filePath, expectedHash);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException {
for (int i = 0; i < isas.length; i++) {
@@ -502,21 +526,21 @@
}
}
- public boolean createProfileSnapshot(int appId, String packageName, String codePath)
- throws InstallerException {
+ public boolean createProfileSnapshot(int appId, String packageName, String profileName,
+ String classpath) throws InstallerException {
if (!checkBeforeRemote()) return false;
try {
- return mInstalld.createProfileSnapshot(appId, packageName, codePath);
+ return mInstalld.createProfileSnapshot(appId, packageName, profileName, classpath);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
- public void destroyProfileSnapshot(String packageName, String codePath)
+ public void destroyProfileSnapshot(String packageName, String profileName)
throws InstallerException {
if (!checkBeforeRemote()) return;
try {
- mInstalld.destroyProfileSnapshot(packageName, codePath);
+ mInstalld.destroyProfileSnapshot(packageName, profileName);
} catch (Exception e) {
throw InstallerException.from(e);
}
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 0395011..5bf38dc 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -261,12 +261,12 @@
String instructionSet, int dexoptNeeded, @Nullable String outputPath,
int dexFlags, String compilerFilter, @Nullable String volumeUuid,
@Nullable String sharedLibraries, @Nullable String seInfo, boolean downgrade,
- int targetSdkVersion)
+ int targetSdkVersion, @Nullable String profileName)
throws InstallerException {
final StringBuilder builder = new StringBuilder();
- // The version. Right now it's 4.
- builder.append("4 ");
+ // The version. Right now it's 5.
+ builder.append("5 ");
builder.append("dexopt");
@@ -283,6 +283,7 @@
encodeParameter(builder, seInfo);
encodeParameter(builder, downgrade);
encodeParameter(builder, targetSdkVersion);
+ encodeParameter(builder, profileName);
commands.add(builder.toString());
}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 6a08e1b..cde8cb7 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
+import android.content.pm.dex.ArtManager;
import android.os.FileUtils;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -111,11 +112,6 @@
return false;
}
- // We do not dexopt a priv-app package when pm.dexopt.priv-apps-oob is true.
- if (pkg.isPrivileged()) {
- return !SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false);
- }
-
return true;
}
@@ -211,12 +207,14 @@
}
}
+ String profileName = ArtManager.getProfileName(i == 0 ? null : pkg.splitNames[i - 1]);
+
final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary()
|| packageUseInfo.isUsedByOtherApps(path);
final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
options.getCompilerFilter(), isUsedByOtherApps);
final boolean profileUpdated = options.isCheckForProfileUpdates() &&
- isProfileUpdated(pkg, sharedGid, compilerFilter);
+ isProfileUpdated(pkg, sharedGid, profileName, compilerFilter);
// Get the dexopt flags after getRealCompilerFilter to make sure we get the correct
// flags.
@@ -225,7 +223,7 @@
for (String dexCodeIsa : dexCodeInstructionSets) {
int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter,
profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid,
- packageStats, options.isDowngrade());
+ packageStats, options.isDowngrade(), profileName);
// The end result is:
// - FAILED if any path failed,
// - PERFORMED if at least one path needed compilation,
@@ -249,7 +247,8 @@
@GuardedBy("mInstallLock")
private int dexOptPath(PackageParser.Package pkg, String path, String isa,
String compilerFilter, boolean profileUpdated, String classLoaderContext,
- int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade) {
+ int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade,
+ String profileName) {
int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext,
profileUpdated, downgrade);
if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
@@ -275,7 +274,8 @@
// primary dex files.
mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
- false /* downgrade*/, pkg.applicationInfo.targetSdkVersion);
+ false /* downgrade*/, pkg.applicationInfo.targetSdkVersion,
+ profileName);
if (packageStats != null) {
long endTime = System.currentTimeMillis();
@@ -396,7 +396,7 @@
mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
/*oatDir*/ null, dexoptFlags,
compilerFilter, info.volumeUuid, classLoaderContext, info.seInfoUser,
- options.isDowngrade(), info.targetSdkVersion);
+ options.isDowngrade(), info.targetSdkVersion, /*profileName*/ null);
}
return DEX_OPT_PERFORMED;
@@ -481,6 +481,11 @@
boolean isUsedByOtherApps) {
int flags = info.flags;
boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
+ // When pm.dexopt.priv-apps-oob is true, we only verify privileged apps.
+ if (info.isPrivilegedApp() &&
+ SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false)) {
+ return "verify";
+ }
if (vmSafeMode) {
return getSafeModeCompilerFilter(targetCompilerFilter);
}
@@ -550,14 +555,15 @@
* current profile and the reference profile will be merged and subsequent calls
* may return a different result.
*/
- private boolean isProfileUpdated(PackageParser.Package pkg, int uid, String compilerFilter) {
+ private boolean isProfileUpdated(PackageParser.Package pkg, int uid, String profileName,
+ String compilerFilter) {
// Check if we are allowed to merge and if the compiler filter is profile guided.
if (!isProfileGuidedCompilerFilter(compilerFilter)) {
return false;
}
// Merge profiles. It returns whether or not there was an updated in the profile info.
try {
- return mInstaller.mergeProfiles(uid, pkg.packageName);
+ return mInstaller.mergeProfiles(uid, pkg.packageName, profileName);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to merge profiles", e);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a73ad8d..837a118 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -52,11 +52,9 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
import static android.content.pm.PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
-import static android.content.pm.PackageManager.INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE;
import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED;
import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
import static android.content.pm.PackageManager.INSTALL_INTERNAL;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
@@ -195,6 +193,7 @@
import android.content.pm.VerifierDeviceIdentity;
import android.content.pm.VerifierInfo;
import android.content.pm.VersionedPackage;
+import android.content.pm.dex.ArtManager;
import android.content.pm.dex.DexMetadataHelper;
import android.content.pm.dex.IArtManager;
import android.content.res.Resources;
@@ -314,6 +313,7 @@
import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
import com.android.server.pm.permission.PermissionsState;
import com.android.server.pm.permission.PermissionsState.PermissionState;
+import com.android.server.security.VerityUtils;
import com.android.server.storage.DeviceStorageMonitorInternal;
import dalvik.system.CloseGuard;
@@ -338,6 +338,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
+import java.security.DigestException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@@ -450,7 +451,6 @@
static final int SCAN_NEW_INSTALL = 1<<2;
static final int SCAN_UPDATE_TIME = 1<<3;
static final int SCAN_BOOTING = 1<<4;
- static final int SCAN_TRUSTED_OVERLAY = 1<<5;
static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<6;
static final int SCAN_REQUIRE_KNOWN = 1<<7;
static final int SCAN_MOVE = 1<<8;
@@ -473,7 +473,6 @@
SCAN_NEW_INSTALL,
SCAN_UPDATE_TIME,
SCAN_BOOTING,
- SCAN_TRUSTED_OVERLAY,
SCAN_DELETE_DATA_ON_FAILURES,
SCAN_REQUIRE_KNOWN,
SCAN_MOVE,
@@ -776,7 +775,7 @@
Collection<PackageParser.Package> allPackages, String targetPackageName) {
List<PackageParser.Package> overlayPackages = null;
for (PackageParser.Package p : allPackages) {
- if (targetPackageName.equals(p.mOverlayTarget) && p.mIsStaticOverlay) {
+ if (targetPackageName.equals(p.mOverlayTarget) && p.mOverlayIsStatic) {
if (overlayPackages == null) {
overlayPackages = new ArrayList<PackageParser.Package>();
}
@@ -860,7 +859,7 @@
void findStaticOverlayPackages() {
synchronized (mPackages) {
for (PackageParser.Package p : mPackages.values()) {
- if (p.mIsStaticOverlay) {
+ if (p.mOverlayIsStatic) {
if (mOverlayPackages == null) {
mOverlayPackages = new ArrayList<PackageParser.Package>();
}
@@ -2561,7 +2560,7 @@
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanFlags
| SCAN_AS_SYSTEM
- | SCAN_TRUSTED_OVERLAY,
+ | SCAN_AS_VENDOR,
0);
mParallelPackageParserCallback.findStaticOverlayPackages();
@@ -8294,11 +8293,13 @@
}
private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg,
- final @ParseFlags int parseFlags, boolean forceCollect) throws PackageManagerException {
+ boolean forceCollect) throws PackageManagerException {
// When upgrading from pre-N MR1, verify the package time stamp using the package
// directory and not the APK file.
final long lastModifiedTime = mIsPreNMR1Upgrade
? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg);
+ // Note that currently skipVerify skips verification on both base and splits for simplicity.
+ final boolean skipVerify = forceCollect && canSkipFullPackageVerification(pkg);
if (ps != null && !forceCollect
&& ps.codePathString.equals(pkg.codePath)
&& ps.timeStamp == lastModifiedTime
@@ -8324,7 +8325,7 @@
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
- PackageParser.collectCertificates(pkg, parseFlags);
+ PackageParser.collectCertificates(pkg, skipVerify);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
} finally {
@@ -8418,6 +8419,48 @@
return scannedPkg;
}
+ /**
+ * Returns if full apk verification can be skipped for the whole package, including the splits.
+ */
+ private boolean canSkipFullPackageVerification(PackageParser.Package pkg) {
+ if (!canSkipFullApkVerification(pkg.baseCodePath)) {
+ return false;
+ }
+ // TODO: Allow base and splits to be verified individually.
+ if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+ for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+ if (!canSkipFullApkVerification(pkg.splitCodePaths[i])) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns if full apk verification can be skipped, depending on current FSVerity setup and
+ * whether the apk contains signed root hash. Note that the signer's certificate still needs to
+ * match one in a trusted source, and should be done separately.
+ */
+ private boolean canSkipFullApkVerification(String apkPath) {
+ byte[] rootHashObserved = null;
+ try {
+ rootHashObserved = VerityUtils.generateFsverityRootHash(apkPath);
+ if (rootHashObserved == null) {
+ return false; // APK does not contain Merkle tree root hash.
+ }
+ synchronized (mInstallLock) {
+ // Returns whether the observed root hash matches what kernel has.
+ mInstaller.assertFsverityRootHashMatches(apkPath, rootHashObserved);
+ return true;
+ }
+ } catch (InstallerException | IOException | DigestException |
+ NoSuchAlgorithmException e) {
+ Slog.w(TAG, "Error in fsverity check. Fallback to full apk verification.", e);
+ }
+ return false;
+ }
+
// Temporary to catch potential issues with refactoring
private static boolean REFACTOR_DEBUG = true;
/**
@@ -8629,10 +8672,11 @@
}
// Verify certificates against what was last scanned. If it is an updated priv app, we will
- // force the verification. Full apk verification will happen unless apk verity is set up for
- // the file. In that case, only small part of the apk is verified upfront.
- collectCertificatesLI(pkgSetting, pkg, parseFlags,
- PackageManagerServiceUtils.isApkVerificationForced(disabledPkgSetting));
+ // force re-collecting certificate. Full apk verification will happen unless apk verity is
+ // set up for the file. In that case, only small part of the apk is verified upfront.
+ final boolean forceCollect = PackageManagerServiceUtils.isApkVerificationForced(
+ disabledPkgSetting);
+ collectCertificatesLI(pkgSetting, pkg, forceCollect);
boolean shouldHideSystemApp = false;
// A new application appeared on /system, but, we already have a copy of
@@ -8873,7 +8917,8 @@
// PackageDexOptimizer to prevent this happening on first boot. The issue
// is that we don't have a good way to say "do this only once".
if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
- pkg.applicationInfo.uid, pkg.packageName)) {
+ pkg.applicationInfo.uid, pkg.packageName,
+ ArtManager.getProfileName(null))) {
Log.e(TAG, "Installer failed to copy system profile!");
} else {
// Disabled as this causes speed-profile compilation during first boot
@@ -8908,7 +8953,8 @@
// issue is that we don't have a good way to say "do this only
// once".
if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
- pkg.applicationInfo.uid, pkg.packageName)) {
+ pkg.applicationInfo.uid, pkg.packageName,
+ ArtManager.getProfileName(null))) {
Log.e(TAG, "Failed to copy system profile for stub package!");
} else {
useProfileForDexopt = true;
@@ -9333,14 +9379,7 @@
synchronized (mInstallLock) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dump profiles");
- final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- try {
- List<String> allCodePaths = pkg.getAllCodePathsExcludingResourceOnly();
- String codePaths = TextUtils.join(";", allCodePaths);
- mInstaller.dumpProfiles(sharedGid, packageName, codePaths);
- } catch (InstallerException e) {
- Slog.w(TAG, "Failed to dump profiles", e);
- }
+ mArtManagerService.dumpProfiles(pkg);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
@@ -9416,6 +9455,8 @@
for (int i = 0; i < childCount; i++) {
clearAppDataLeafLIF(pkg.childPackages.get(i), userId, flags);
}
+
+ clearAppProfilesLIF(pkg, UserHandle.USER_ALL);
}
private void clearAppDataLeafLIF(PackageParser.Package pkg, int userId, int flags) {
@@ -9488,18 +9529,10 @@
Slog.wtf(TAG, "Package was null!", new Throwable());
return;
}
- clearAppProfilesLeafLIF(pkg);
+ mArtManagerService.clearAppProfiles(pkg);
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
- clearAppProfilesLeafLIF(pkg.childPackages.get(i));
- }
- }
-
- private void clearAppProfilesLeafLIF(PackageParser.Package pkg) {
- try {
- mInstaller.clearAppProfiles(pkg.packageName);
- } catch (InstallerException e) {
- Slog.w(TAG, String.valueOf(e));
+ mArtManagerService.clearAppProfiles(pkg.childPackages.get(i));
}
}
@@ -10616,7 +10649,6 @@
}
}
}
- pkg.mTrustedOverlay = (scanFlags & SCAN_TRUSTED_OVERLAY) != 0;
if ((scanFlags & SCAN_AS_PRIVILEGED) != 0) {
pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
@@ -10638,6 +10670,14 @@
}
}
+ private static @NonNull <T> T assertNotNull(@Nullable T object, String message)
+ throws PackageManagerException {
+ if (object == null) {
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, message);
+ }
+ return object;
+ }
+
/**
* Asserts the parsed package is valid according to the given policy. If the
* package is invalid, for whatever reason, throws {@link PackageManagerException}.
@@ -10911,6 +10951,50 @@
}
}
}
+
+ // Apply policies specific for runtime resource overlays (RROs).
+ if (pkg.mOverlayTarget != null) {
+ // System overlays have some restrictions on their use of the 'static' state.
+ if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
+ // We are scanning a system overlay. This can be the first scan of the
+ // system/vendor/oem partition, or an update to the system overlay.
+ if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ // This must be an update to a system overlay.
+ final PackageSetting previousPkg = assertNotNull(
+ mSettings.getPackageLPr(pkg.packageName),
+ "previous package state not present");
+
+ // Static overlays cannot be updated.
+ if (previousPkg.pkg.mOverlayIsStatic) {
+ throw new PackageManagerException("Overlay " + pkg.packageName +
+ " is static and cannot be upgraded.");
+ // Non-static overlays cannot be converted to static overlays.
+ } else if (pkg.mOverlayIsStatic) {
+ throw new PackageManagerException("Overlay " + pkg.packageName +
+ " cannot be upgraded into a static overlay.");
+ }
+ }
+ } else {
+ // The overlay is a non-system overlay. Non-system overlays cannot be static.
+ if (pkg.mOverlayIsStatic) {
+ throw new PackageManagerException("Overlay " + pkg.packageName +
+ " is static but not pre-installed.");
+ }
+
+ // The only case where we allow installation of a non-system overlay is when
+ // its signature is signed with the platform certificate.
+ PackageSetting platformPkgSetting = mSettings.getPackageLPr("android");
+ if ((platformPkgSetting.signatures.mSigningDetails
+ != PackageParser.SigningDetails.UNKNOWN)
+ && (compareSignatures(
+ platformPkgSetting.signatures.mSigningDetails.signatures,
+ pkg.mSigningDetails.signatures)
+ != PackageManager.SIGNATURE_MATCH)) {
+ throw new PackageManagerException("Overlay " + pkg.packageName +
+ " must be signed with the platform certificate.");
+ }
+ }
+ }
}
}
@@ -16108,7 +16192,6 @@
clearAppDataLIF(pkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE
| StorageManager.FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
- clearAppProfilesLIF(deletedPackage, UserHandle.USER_ALL);
try {
final PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags,
@@ -16247,7 +16330,6 @@
// Successfully disabled the old package. Now proceed with re-installation
clearAppDataLIF(pkg, UserHandle.USER_ALL, StorageManager.FLAG_STORAGE_DE
| StorageManager.FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
- clearAppProfilesLIF(deletedPackage, UserHandle.USER_ALL);
res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
pkg.setApplicationInfoFlags(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP,
@@ -16707,7 +16789,7 @@
if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) {
pkg.setSigningDetails(args.signingDetails);
} else {
- PackageParser.collectCertificates(pkg, parseFlags);
+ PackageParser.collectCertificates(pkg, false /* skipVerify */);
}
} catch (PackageParserException e) {
res.setError("Failed collect during installPackageLI", e);
@@ -16987,6 +17069,43 @@
return;
}
+ if (PackageManagerServiceUtils.isApkVerityEnabled()) {
+ String apkPath = null;
+ synchronized (mPackages) {
+ // Note that if the attacker managed to skip verify setup, for example by tampering
+ // with the package settings, upon reboot we will do full apk verification when
+ // verity is not detected.
+ final PackageSetting ps = mSettings.mPackages.get(pkgName);
+ if (ps != null && ps.isPrivileged()) {
+ apkPath = pkg.baseCodePath;
+ }
+ }
+
+ if (apkPath != null) {
+ final VerityUtils.SetupResult result =
+ VerityUtils.generateApkVeritySetupData(apkPath);
+ if (result.isOk()) {
+ if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling apk verity to " + apkPath);
+ FileDescriptor fd = result.getUnownedFileDescriptor();
+ try {
+ mInstaller.installApkVerity(apkPath, fd);
+ } catch (InstallerException e) {
+ res.setError(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to set up verity: " + e);
+ return;
+ } finally {
+ IoUtils.closeQuietly(fd);
+ }
+ } else if (result.isFailed()) {
+ res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Failed to generate verity");
+ return;
+ } else {
+ // Do nothing if verity is skipped. Will fall back to full apk verification on
+ // reboot.
+ }
+ }
+ }
+
if (!instantApp) {
startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
} else {
@@ -17022,7 +17141,7 @@
// Prepare the application profiles for the new code paths.
// This needs to be done before invoking dexopt so that any install-time profile
// can be used for optimizations.
- mArtManagerService.prepareAppProfiles(pkg, args.user.getIdentifier());
+ mArtManagerService.prepareAppProfiles(pkg, resolveUserIds(args.user.getIdentifier()));
// Check whether we need to dexopt the app.
//
@@ -20283,7 +20402,6 @@
}
clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE
| FLAG_STORAGE_CE | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
- clearAppProfilesLIF(pkg, UserHandle.USER_ALL);
mDexManager.notifyPackageUpdated(pkg.packageName,
pkg.baseCodePath, pkg.splitCodePaths);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 021c4b8..76c199b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -562,8 +562,7 @@
private static boolean matchSignatureInSystem(PackageSetting pkgSetting,
PackageSetting disabledPkgSetting) {
try {
- PackageParser.collectCertificates(disabledPkgSetting.pkg,
- PackageParser.PARSE_IS_SYSTEM_DIR);
+ PackageParser.collectCertificates(disabledPkgSetting.pkg, true /* skipVerify */);
if (compareSignatures(pkgSetting.signatures.mSigningDetails.signatures,
disabledPkgSetting.signatures.mSigningDetails.signatures)
!= PackageManager.SIGNATURE_MATCH) {
@@ -579,7 +578,6 @@
return true;
}
-
/**
* Checks the signing certificates to see if the provided certificate is a member. Invalid for
* {@code SigningDetails} with multiple signing certificates.
@@ -642,10 +640,14 @@
return false;
}
+ /** Returns true if APK Verity is enabled. */
+ static boolean isApkVerityEnabled() {
+ return SystemProperties.getInt("ro.apk_verity.mode", 0) != 0;
+ }
+
/** Returns true to force apk verification if the updated package (in /data) is a priv app. */
static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) {
- return disabledPs != null && disabledPs.isPrivileged() &&
- SystemProperties.getInt("ro.apk_verity.mode", 0) != 0;
+ return disabledPs != null && disabledPs.isPrivileged() && isApkVerityEnabled();
}
/**
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 92d159b..e290272 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -23,9 +23,10 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.dex.ArtManager;
+import android.content.pm.dex.ArtManager.ProfileType;
import android.content.pm.dex.DexMetadataHelper;
import android.os.Binder;
-import android.os.Environment;
+import android.os.Build;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -33,6 +34,7 @@
import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.system.Os;
import android.util.ArrayMap;
import android.util.Slog;
@@ -44,6 +46,9 @@
import com.android.server.pm.Installer.InstallerException;
import java.io.File;
import java.io.FileNotFoundException;
+import libcore.io.IoUtils;
+import libcore.util.NonNull;
+import libcore.util.Nullable;
/**
* A system service that provides access to runtime and compiler artifacts.
@@ -63,6 +68,12 @@
private static boolean DEBUG = false;
private static boolean DEBUG_IGNORE_PERMISSIONS = false;
+ // Package name used to create the profile directory layout when
+ // taking a snapshot of the boot image profile.
+ private static final String BOOT_IMAGE_ANDROID_PACKAGE = "android";
+ // Profile name used for the boot image profile.
+ private static final String BOOT_IMAGE_PROFILE_NAME = "android.prof";
+
private final IPackageManager mPackageManager;
private final Object mInstallLock;
@GuardedBy("mInstallLock")
@@ -78,20 +89,36 @@
}
@Override
- public void snapshotRuntimeProfile(String packageName, String codePath,
- ISnapshotRuntimeProfileCallback callback) {
+ public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName,
+ @Nullable String codePath, @NonNull ISnapshotRuntimeProfileCallback callback) {
// Sanity checks on the arguments.
- Preconditions.checkStringNotEmpty(packageName);
- Preconditions.checkStringNotEmpty(codePath);
Preconditions.checkNotNull(callback);
- // Verify that the caller has the right permissions.
- checkReadRuntimeProfilePermission();
+ boolean bootImageProfile = profileType == ArtManager.PROFILE_BOOT_IMAGE;
+ if (!bootImageProfile) {
+ Preconditions.checkStringNotEmpty(codePath);
+ Preconditions.checkStringNotEmpty(packageName);
+ }
+
+ // Verify that the caller has the right permissions and that the runtime profiling is
+ // enabled. The call to isRuntimePermissions will checkReadRuntimeProfilePermission.
+ if (!isRuntimeProfilingEnabled(profileType)) {
+ throw new IllegalStateException("Runtime profiling is not enabled for " + profileType);
+ }
if (DEBUG) {
Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
}
+ if (bootImageProfile) {
+ snapshotBootImageProfile(callback);
+ } else {
+ snapshotAppProfile(packageName, codePath, callback);
+ }
+ }
+
+ private void snapshotAppProfile(String packageName, String codePath,
+ ISnapshotRuntimeProfileCallback callback) {
PackageInfo info = null;
try {
// Note that we use the default user 0 to retrieve the package info.
@@ -111,11 +138,13 @@
}
boolean pathFound = info.applicationInfo.getBaseCodePath().equals(codePath);
+ String splitName = null;
String[] splitCodePaths = info.applicationInfo.getSplitCodePaths();
if (!pathFound && (splitCodePaths != null)) {
- for (String path : splitCodePaths) {
- if (path.equals(codePath)) {
+ for (int i = splitCodePaths.length - 1; i >= 0; i--) {
+ if (splitCodePaths[i].equals(codePath)) {
pathFound = true;
+ splitName = info.applicationInfo.splitNames[i];
break;
}
}
@@ -126,18 +155,25 @@
}
// All good, create the profile snapshot.
- createProfileSnapshot(packageName, codePath, callback, info);
+ int appId = UserHandle.getAppId(info.applicationInfo.uid);
+ if (appId < 0) {
+ postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
+ return;
+ }
+
+ createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
+ appId, callback);
// Destroy the snapshot, we no longer need it.
- destroyProfileSnapshot(packageName, codePath);
+ destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
}
- private void createProfileSnapshot(String packageName, String codePath,
- ISnapshotRuntimeProfileCallback callback, PackageInfo info) {
+ private void createProfileSnapshot(String packageName, String profileName, String classpath,
+ int appId, ISnapshotRuntimeProfileCallback callback) {
// Ask the installer to snapshot the profile.
synchronized (mInstallLock) {
try {
- if (!mInstaller.createProfileSnapshot(UserHandle.getAppId(info.applicationInfo.uid),
- packageName, codePath)) {
+ if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
return;
}
@@ -148,38 +184,64 @@
}
// Open the snapshot and invoke the callback.
- File snapshotProfile = Environment.getProfileSnapshotPath(packageName, codePath);
- ParcelFileDescriptor fd;
+ File snapshotProfile = ArtManager.getProfileSnapshotFileForName(packageName, profileName);
+
+ ParcelFileDescriptor fd = null;
try {
fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
postSuccess(packageName, fd, callback);
} catch (FileNotFoundException e) {
- Slog.w(TAG, "Could not open snapshot profile for " + packageName + ":" + codePath, e);
+ Slog.w(TAG, "Could not open snapshot profile for " + packageName + ":"
+ + snapshotProfile, e);
postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
+ } finally {
+ IoUtils.closeQuietly(fd);
}
}
- private void destroyProfileSnapshot(String packageName, String codePath) {
+ private void destroyProfileSnapshot(String packageName, String profileName) {
if (DEBUG) {
- Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + codePath);
+ Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
}
synchronized (mInstallLock) {
try {
- mInstaller.destroyProfileSnapshot(packageName, codePath);
+ mInstaller.destroyProfileSnapshot(packageName, profileName);
} catch (InstallerException e) {
Slog.e(TAG, "Failed to destroy profile snapshot for " +
- packageName + ":" + codePath, e);
+ packageName + ":" + profileName, e);
}
}
}
@Override
- public boolean isRuntimeProfilingEnabled() {
+ public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) {
// Verify that the caller has the right permissions.
checkReadRuntimeProfilePermission();
- return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+ switch (profileType) {
+ case ArtManager.PROFILE_APPS :
+ return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+ case ArtManager.PROFILE_BOOT_IMAGE:
+ return (Build.IS_USERDEBUG || Build.IS_ENG) &&
+ SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false) &&
+ SystemProperties.getBoolean("dalvik.vm.profilebootimage", false);
+ default:
+ throw new IllegalArgumentException("Invalid profile type:" + profileType);
+ }
+ }
+
+ private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback) {
+ // Combine the profiles for boot classpath and system server classpath.
+ // This avoids having yet another type of profiles and simplifies the processing.
+ String classpath = String.join(":", Os.getenv("BOOTCLASSPATH"),
+ Os.getenv("SYSTEMSERVERCLASSPATH"));
+
+ // Create the snapshot.
+ createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME, classpath,
+ /*appId*/ -1, callback);
+ // Destroy the snapshot, we no longer need it.
+ destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
}
/**
@@ -245,6 +307,14 @@
*/
public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
+ if (user < 0) {
+ Slog.wtf(TAG, "Invalid user id: " + user);
+ return;
+ }
+ if (appId < 0) {
+ Slog.wtf(TAG, "Invalid app id: " + appId);
+ return;
+ }
try {
ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
@@ -267,6 +337,49 @@
}
/**
+ * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
+ */
+ public void prepareAppProfiles(PackageParser.Package pkg, int[] user) {
+ for (int i = 0; i < user.length; i++) {
+ prepareAppProfiles(pkg, user[i]);
+ }
+ }
+
+ /**
+ * Clear the profiles for the given package.
+ */
+ public void clearAppProfiles(PackageParser.Package pkg) {
+ try {
+ ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
+ for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
+ String profileName = packageProfileNames.valueAt(i);
+ mInstaller.clearAppProfiles(pkg.packageName, profileName);
+ }
+ } catch (InstallerException e) {
+ Slog.w(TAG, String.valueOf(e));
+ }
+ }
+
+ /**
+ * Dumps the profiles for the given package.
+ */
+ public void dumpProfiles(PackageParser.Package pkg) {
+ final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+ try {
+ ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
+ for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
+ String codePath = packageProfileNames.keyAt(i);
+ String profileName = packageProfileNames.valueAt(i);
+ synchronized (mInstallLock) {
+ mInstaller.dumpProfiles(sharedGid, pkg.packageName, profileName, codePath);
+ }
+ }
+ } catch (InstallerException e) {
+ Slog.w(TAG, "Failed to dump profiles", e);
+ }
+ }
+
+ /**
* Build the profiles names for all the package code paths (excluding resource only paths).
* Return the map [code path -> profile name].
*/
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index fbdedce..cf36166 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -70,6 +70,7 @@
import android.service.vr.IVrStateCallbacks;
import android.util.EventLog;
import android.util.KeyValueListParser;
+import android.util.MathUtils;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
@@ -458,19 +459,11 @@
private int mScreenBrightnessSettingMinimum;
private int mScreenBrightnessSettingMaximum;
private int mScreenBrightnessSettingDefault;
- private int mScreenBrightnessForVrSettingDefault;
// The screen brightness setting, from 0 to 255.
// Use -1 if no value has been set.
private int mScreenBrightnessSetting;
- // The screen brightness setting, from 0 to 255, to be used while in VR Mode.
- private int mScreenBrightnessForVrSetting;
-
- // The screen auto-brightness adjustment setting, from -1 to 1.
- // Use 0 if there is no adjustment.
- private float mScreenAutoBrightnessAdjustmentSetting;
-
// The screen brightness mode.
// One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants.
private int mScreenBrightnessModeSetting;
@@ -493,17 +486,6 @@
// Use -1 to disable.
private long mUserActivityTimeoutOverrideFromWindowManager = -1;
- // The screen brightness setting override from the settings application
- // to temporarily adjust the brightness until next updated,
- // Use -1 to disable.
- private int mTemporaryScreenBrightnessSettingOverride = -1;
-
- // The screen brightness adjustment setting override from the settings
- // application to temporarily adjust the auto-brightness adjustment factor
- // until next updated, in the range -1..1.
- // Use NaN to disable.
- private float mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = Float.NaN;
-
// The screen state to use while dozing.
private int mDozeScreenStateOverrideFromDreamManager = Display.STATE_UNKNOWN;
@@ -774,7 +756,6 @@
mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting();
mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting();
mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting();
- mScreenBrightnessForVrSettingDefault = pm.getDefaultScreenBrightnessForVrSetting();
SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper());
@@ -837,12 +818,6 @@
Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
false, mSettingsObserver, UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.SCREEN_BRIGHTNESS),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.SCREEN_BRIGHTNESS_FOR_VR),
- false, mSettingsObserver, UserHandle.USER_ALL);
- resolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.SCREEN_BRIGHTNESS_MODE),
false, mSettingsObserver, UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.System.getUriFor(
@@ -978,29 +953,6 @@
SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, retailDemoValue);
}
- final int oldScreenBrightnessSetting = getCurrentBrightnessSettingLocked();
-
- mScreenBrightnessForVrSetting = Settings.System.getIntForUser(resolver,
- Settings.System.SCREEN_BRIGHTNESS_FOR_VR, mScreenBrightnessForVrSettingDefault,
- UserHandle.USER_CURRENT);
-
- mScreenBrightnessSetting = Settings.System.getIntForUser(resolver,
- Settings.System.SCREEN_BRIGHTNESS, mScreenBrightnessSettingDefault,
- UserHandle.USER_CURRENT);
-
- if (oldScreenBrightnessSetting != getCurrentBrightnessSettingLocked()) {
- mTemporaryScreenBrightnessSettingOverride = -1;
- }
-
- final float oldScreenAutoBrightnessAdjustmentSetting =
- mScreenAutoBrightnessAdjustmentSetting;
- mScreenAutoBrightnessAdjustmentSetting = Settings.System.getFloatForUser(resolver,
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f,
- UserHandle.USER_CURRENT);
- if (oldScreenAutoBrightnessAdjustmentSetting != mScreenAutoBrightnessAdjustmentSetting) {
- mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = Float.NaN;
- }
-
mScreenBrightnessModeSetting = Settings.System.getIntForUser(resolver,
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
@@ -1019,10 +971,6 @@
mDirty |= DIRTY_SETTINGS;
}
- private int getCurrentBrightnessSettingLocked() {
- return mIsVrModeEnabled ? mScreenBrightnessForVrSetting : mScreenBrightnessSetting;
- }
-
private void postAfterBootCompleted(Runnable r) {
if (mBootCompleted) {
BackgroundThread.getHandler().post(r);
@@ -2450,53 +2398,24 @@
mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked();
// Determine appropriate screen brightness and auto-brightness adjustments.
- boolean brightnessSetByUser = true;
- int screenBrightness = mScreenBrightnessSettingDefault;
- float screenAutoBrightnessAdjustment = 0.0f;
- boolean autoBrightness = (mScreenBrightnessModeSetting ==
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
- boolean brightnessIsTemporary = false;
+ final boolean autoBrightness;
+ final int screenBrightnessOverride;
if (!mBootCompleted) {
// Keep the brightness steady during boot. This requires the
// bootloader brightness and the default brightness to be identical.
autoBrightness = false;
- brightnessSetByUser = false;
- } else if (mIsVrModeEnabled) {
- screenBrightness = mScreenBrightnessForVrSetting;
- autoBrightness = false;
+ screenBrightnessOverride = mScreenBrightnessSettingDefault;
} else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
- screenBrightness = mScreenBrightnessOverrideFromWindowManager;
autoBrightness = false;
- brightnessSetByUser = false;
- } else if (isValidBrightness(mTemporaryScreenBrightnessSettingOverride)) {
- screenBrightness = mTemporaryScreenBrightnessSettingOverride;
- brightnessIsTemporary = true;
- } else if (isValidBrightness(mScreenBrightnessSetting)) {
- screenBrightness = mScreenBrightnessSetting;
+ screenBrightnessOverride = mScreenBrightnessOverrideFromWindowManager;
+ } else {
+ autoBrightness = (mScreenBrightnessModeSetting ==
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ screenBrightnessOverride = -1;
}
- if (autoBrightness) {
- screenBrightness = mScreenBrightnessSettingDefault;
- if (isValidAutoBrightnessAdjustment(
- mTemporaryScreenAutoBrightnessAdjustmentSettingOverride)) {
- screenAutoBrightnessAdjustment =
- mTemporaryScreenAutoBrightnessAdjustmentSettingOverride;
- brightnessIsTemporary = true;
- } else if (isValidAutoBrightnessAdjustment(
- mScreenAutoBrightnessAdjustmentSetting)) {
- screenAutoBrightnessAdjustment = mScreenAutoBrightnessAdjustmentSetting;
- }
- }
- screenBrightness = Math.max(Math.min(screenBrightness,
- mScreenBrightnessSettingMaximum), mScreenBrightnessSettingMinimum);
- screenAutoBrightnessAdjustment = Math.max(Math.min(
- screenAutoBrightnessAdjustment, 1.0f), -1.0f);
// Update display power request.
- mDisplayPowerRequest.screenBrightness = screenBrightness;
- mDisplayPowerRequest.screenAutoBrightnessAdjustment =
- screenAutoBrightnessAdjustment;
- mDisplayPowerRequest.brightnessSetByUser = brightnessSetByUser;
- mDisplayPowerRequest.brightnessIsTemporary = brightnessIsTemporary;
+ mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
mDisplayPowerRequest.useAutoBrightness = autoBrightness;
mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness();
@@ -2534,6 +2453,8 @@
+ ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)
+ ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)
+ ", mBootCompleted=" + mBootCompleted
+ + ", screenBrightnessOverride=" + screenBrightnessOverride
+ + ", useAutoBrightness=" + autoBrightness
+ ", mScreenBrightnessBoostInProgress=" + mScreenBrightnessBoostInProgress
+ ", mIsVrModeEnabled= " + mIsVrModeEnabled
+ ", sQuiescent=" + sQuiescent);
@@ -2573,11 +2494,6 @@
return value >= 0 && value <= 255;
}
- private static boolean isValidAutoBrightnessAdjustment(float value) {
- // Handles NaN by always returning false.
- return value >= -1.0f && value <= 1.0f;
- }
-
@VisibleForTesting
int getDesiredScreenPolicyLocked() {
if (mWakefulness == WAKEFULNESS_ASLEEP || sQuiescent) {
@@ -3247,28 +3163,6 @@
}
}
- private void setTemporaryScreenBrightnessSettingOverrideInternal(int brightness) {
- synchronized (mLock) {
- if (mTemporaryScreenBrightnessSettingOverride != brightness) {
- mTemporaryScreenBrightnessSettingOverride = brightness;
- mDirty |= DIRTY_SETTINGS;
- updatePowerStateLocked();
- }
- }
- }
-
- private void setTemporaryScreenAutoBrightnessAdjustmentSettingOverrideInternal(float adj) {
- synchronized (mLock) {
- // Note: This condition handles NaN because NaN is not equal to any other
- // value, including itself.
- if (mTemporaryScreenAutoBrightnessAdjustmentSettingOverride != adj) {
- mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = adj;
- mDirty |= DIRTY_SETTINGS;
- updatePowerStateLocked();
- }
- }
- }
-
private void setDozeOverrideFromDreamManagerInternal(
int screenState, int screenBrightness) {
synchronized (mLock) {
@@ -3478,8 +3372,6 @@
+ isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")");
pw.println(" mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting);
pw.println(" mScreenBrightnessSetting=" + mScreenBrightnessSetting);
- pw.println(" mScreenAutoBrightnessAdjustmentSetting="
- + mScreenAutoBrightnessAdjustmentSetting);
pw.println(" mScreenBrightnessModeSetting=" + mScreenBrightnessModeSetting);
pw.println(" mScreenBrightnessOverrideFromWindowManager="
+ mScreenBrightnessOverrideFromWindowManager);
@@ -3487,10 +3379,6 @@
+ mUserActivityTimeoutOverrideFromWindowManager);
pw.println(" mUserInactiveOverrideFromWindowManager="
+ mUserInactiveOverrideFromWindowManager);
- pw.println(" mTemporaryScreenBrightnessSettingOverride="
- + mTemporaryScreenBrightnessSettingOverride);
- pw.println(" mTemporaryScreenAutoBrightnessAdjustmentSettingOverride="
- + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride);
pw.println(" mDozeScreenStateOverrideFromDreamManager="
+ mDozeScreenStateOverrideFromDreamManager);
pw.println(" mDozeScreenBrightnessOverrideFromDreamManager="
@@ -3498,9 +3386,6 @@
pw.println(" mScreenBrightnessSettingMinimum=" + mScreenBrightnessSettingMinimum);
pw.println(" mScreenBrightnessSettingMaximum=" + mScreenBrightnessSettingMaximum);
pw.println(" mScreenBrightnessSettingDefault=" + mScreenBrightnessSettingDefault);
- pw.println(" mScreenBrightnessForVrSettingDefault="
- + mScreenBrightnessForVrSettingDefault);
- pw.println(" mScreenBrightnessForVrSetting=" + mScreenBrightnessForVrSetting);
pw.println(" mDoubleTapWakeEnabled=" + mDoubleTapWakeEnabled);
pw.println(" mIsVrModeEnabled=" + mIsVrModeEnabled);
pw.println(" mForegroundProfile=" + mForegroundProfile);
@@ -3812,13 +3697,6 @@
proto.end(stayOnWhilePluggedInToken);
proto.write(
- PowerServiceSettingsAndConfigurationDumpProto.SCREEN_BRIGHTNESS_SETTING,
- mScreenBrightnessSetting);
- proto.write(
- PowerServiceSettingsAndConfigurationDumpProto
- .SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_SETTING,
- mScreenAutoBrightnessAdjustmentSetting);
- proto.write(
PowerServiceSettingsAndConfigurationDumpProto.SCREEN_BRIGHTNESS_MODE_SETTING,
mScreenBrightnessModeSetting);
proto.write(
@@ -3835,14 +3713,6 @@
mUserInactiveOverrideFromWindowManager);
proto.write(
PowerServiceSettingsAndConfigurationDumpProto
- .TEMPORARY_SCREEN_BRIGHTNESS_SETTING_OVERRIDE,
- mTemporaryScreenBrightnessSettingOverride);
- proto.write(
- PowerServiceSettingsAndConfigurationDumpProto
- .TEMPORARY_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_SETTING_OVERRIDE,
- mTemporaryScreenAutoBrightnessAdjustmentSettingOverride);
- proto.write(
- PowerServiceSettingsAndConfigurationDumpProto
.DOZE_SCREEN_STATE_OVERRIDE_FROM_DREAM_MANAGER,
mDozeScreenStateOverrideFromDreamManager);
proto.write(
@@ -3866,16 +3736,9 @@
PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto
.SETTING_DEFAULT,
mScreenBrightnessSettingDefault);
- proto.write(
- PowerServiceSettingsAndConfigurationDumpProto.ScreenBrightnessSettingLimitsProto
- .SETTING_FOR_VR_DEFAULT,
- mScreenBrightnessForVrSettingDefault);
proto.end(screenBrightnessSettingLimitsToken);
proto.write(
- PowerServiceSettingsAndConfigurationDumpProto.SCREEN_BRIGHTNESS_FOR_VR_SETTING,
- mScreenBrightnessForVrSetting);
- proto.write(
PowerServiceSettingsAndConfigurationDumpProto.IS_DOUBLE_TAP_WAKE_ENABLED,
mDoubleTapWakeEnabled);
proto.write(
@@ -4709,56 +4572,6 @@
}
/**
- * Used by the settings application and brightness control widgets to
- * temporarily override the current screen brightness setting so that the
- * user can observe the effect of an intended settings change without applying
- * it immediately.
- *
- * The override will be canceled when the setting value is next updated.
- *
- * @param brightness The overridden brightness.
- *
- * @see android.provider.Settings.System#SCREEN_BRIGHTNESS
- */
- @Override // Binder call
- public void setTemporaryScreenBrightnessSettingOverride(int brightness) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.DEVICE_POWER, null);
-
- final long ident = Binder.clearCallingIdentity();
- try {
- setTemporaryScreenBrightnessSettingOverrideInternal(brightness);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
- * Used by the settings application and brightness control widgets to
- * temporarily override the current screen auto-brightness adjustment setting so that the
- * user can observe the effect of an intended settings change without applying
- * it immediately.
- *
- * The override will be canceled when the setting value is next updated.
- *
- * @param adj The overridden brightness, or Float.NaN to disable the override.
- *
- * @see android.provider.Settings.System#SCREEN_AUTO_BRIGHTNESS_ADJ
- */
- @Override // Binder call
- public void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float adj) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.DEVICE_POWER, null);
-
- final long ident = Binder.clearCallingIdentity();
- try {
- setTemporaryScreenAutoBrightnessAdjustmentSettingOverrideInternal(adj);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- }
-
- /**
* Used by the phone application to make the attention LED flash when ringing.
*/
@Override // Binder call
diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java
new file mode 100644
index 0000000..d2d0e60
--- /dev/null
+++ b/services/core/java/com/android/server/security/VerityUtils.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.security;
+
+import static android.system.OsConstants.PROT_READ;
+import static android.system.OsConstants.PROT_WRITE;
+
+import android.annotation.NonNull;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.apk.ApkSignatureVerifier;
+import android.util.apk.ByteBufferFactory;
+import android.util.apk.SignatureNotFoundException;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/** Provides fsverity related operations. */
+abstract public class VerityUtils {
+ private static final String TAG = "VerityUtils";
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * Generates Merkle tree and fsverity metadata.
+ *
+ * @return {@code SetupResult} that contains the {@code EsetupResultCode}, and when success, the
+ * {@code FileDescriptor} to read all the data from.
+ */
+ public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
+ if (DEBUG) Slog.d(TAG, "Trying to install apk verity to " + apkPath);
+ SharedMemory shm = null;
+ try {
+ byte[] signedRootHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
+ if (signedRootHash == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Skip verity tree generation since there is no root hash");
+ }
+ return SetupResult.skipped();
+ }
+
+ shm = generateApkVerityIntoSharedMemory(apkPath, signedRootHash);
+ FileDescriptor rfd = shm.getFileDescriptor();
+ if (rfd == null || !rfd.valid()) {
+ return SetupResult.failed();
+ }
+ return SetupResult.ok(Os.dup(rfd));
+ } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException |
+ SignatureNotFoundException | ErrnoException e) {
+ Slog.e(TAG, "Failed to set up apk verity: ", e);
+ return SetupResult.failed();
+ } finally {
+ if (shm != null) {
+ shm.close();
+ }
+ }
+ }
+
+ /**
+ * {@see ApkSignatureVerifier#generateFsverityRootHash(String)}.
+ */
+ public static byte[] generateFsverityRootHash(@NonNull String apkPath)
+ throws NoSuchAlgorithmException, DigestException, IOException {
+ return ApkSignatureVerifier.generateFsverityRootHash(apkPath);
+ }
+
+ /**
+ * Returns a {@code SharedMemory} that contains Merkle tree and fsverity headers for the given
+ * apk, in the form that can immediately be used for fsverity setup.
+ */
+ private static SharedMemory generateApkVerityIntoSharedMemory(
+ String apkPath, byte[] expectedRootHash)
+ throws IOException, SecurityException, DigestException, NoSuchAlgorithmException,
+ SignatureNotFoundException {
+ TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory();
+ byte[] generatedRootHash = ApkSignatureVerifier.generateApkVerity(apkPath,
+ shmBufferFactory);
+ // We only generate Merkle tree once here, so it's important to make sure the root hash
+ // matches the signed one in the apk.
+ if (!Arrays.equals(expectedRootHash, generatedRootHash)) {
+ throw new SecurityException("Locally generated verity root hash does not match");
+ }
+
+ SharedMemory shm = shmBufferFactory.releaseSharedMemory();
+ if (shm == null) {
+ throw new IllegalStateException("Failed to generate verity tree into shared memory");
+ }
+ if (!shm.setProtect(PROT_READ)) {
+ throw new SecurityException("Failed to set up shared memory correctly");
+ }
+ return shm;
+ }
+
+ public static class SetupResult {
+ /** Result code if verity is set up correctly. */
+ private static final int RESULT_OK = 1;
+
+ /** Result code if the apk does not contain a verity root hash. */
+ private static final int RESULT_SKIPPED = 2;
+
+ /** Result code if the setup failed. */
+ private static final int RESULT_FAILED = 3;
+
+ private final int mCode;
+ private final FileDescriptor mFileDescriptor;
+
+ public static SetupResult ok(@NonNull FileDescriptor fileDescriptor) {
+ return new SetupResult(RESULT_OK, fileDescriptor);
+ }
+
+ public static SetupResult skipped() {
+ return new SetupResult(RESULT_SKIPPED, null);
+ }
+
+ public static SetupResult failed() {
+ return new SetupResult(RESULT_FAILED, null);
+ }
+
+ private SetupResult(int code, FileDescriptor fileDescriptor) {
+ this.mCode = code;
+ this.mFileDescriptor = fileDescriptor;
+ }
+
+ public boolean isFailed() {
+ return mCode == RESULT_FAILED;
+ }
+
+ public boolean isOk() {
+ return mCode == RESULT_OK;
+ }
+
+ public @NonNull FileDescriptor getUnownedFileDescriptor() {
+ return mFileDescriptor;
+ }
+ }
+
+ /** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */
+ private static class TrackedShmBufferFactory implements ByteBufferFactory {
+ private SharedMemory mShm;
+ private ByteBuffer mBuffer;
+
+ @Override
+ public ByteBuffer create(int capacity) throws SecurityException {
+ try {
+ if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity");
+ // NB: This method is supposed to be called once according to the contract with
+ // ApkSignatureSchemeV2Verifier.
+ if (mBuffer != null) {
+ throw new IllegalStateException("Multiple instantiation from this factory");
+ }
+ mShm = SharedMemory.create("apkverity", capacity);
+ if (!mShm.setProtect(PROT_READ | PROT_WRITE)) {
+ throw new SecurityException("Failed to set protection");
+ }
+ mBuffer = mShm.mapReadWrite();
+ return mBuffer;
+ } catch (ErrnoException e) {
+ throw new SecurityException("Failed to set protection", e);
+ }
+ }
+
+ public SharedMemory releaseSharedMemory() {
+ if (mBuffer != null) {
+ SharedMemory.unmap(mBuffer);
+ mBuffer = null;
+ }
+ SharedMemory tmp = mShm;
+ mShm = null;
+ return tmp;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index f82dc24..faafb39 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -18,16 +18,19 @@
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.PendingIntent;
+import android.app.StatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.net.NetworkStats;
import android.net.wifi.IWifiManager;
import android.net.wifi.WifiActivityEnergyInfo;
+import android.os.StatsDimensionsValue;
import android.os.BatteryStatsInternal;
import android.os.Binder;
import android.os.Bundle;
@@ -80,9 +83,12 @@
static final String TAG = "StatsCompanionService";
static final boolean DEBUG = true;
+
public static final String ACTION_TRIGGER_COLLECTION =
"com.android.server.stats.action.TRIGGER_COLLECTION";
+ public static final int CODE_SUBSCRIBER_BROADCAST = 1;
+
private final Context mContext;
private final AlarmManager mAlarmManager;
@GuardedBy("sStatsdLock")
@@ -151,10 +157,37 @@
@Override
public void sendBroadcast(String pkg, String cls) {
+ // TODO: Use a pending intent, and enfoceCallingPermission.
mContext.sendBroadcastAsUser(new Intent(ACTION_TRIGGER_COLLECTION).setClassName(pkg, cls),
UserHandle.SYSTEM);
}
+ @Override
+ public void sendSubscriberBroadcast(IBinder intentSenderBinder, long configUid, long configKey,
+ long subscriptionId, long subscriptionRuleId,
+ StatsDimensionsValue dimensionsValue) {
+ if (DEBUG) Slog.d(TAG, "Statsd requested to sendSubscriberBroadcast.");
+ enforceCallingPermission();
+ IntentSender intentSender = new IntentSender(intentSenderBinder);
+ Intent intent = new Intent()
+ .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid)
+ .putExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, configKey)
+ .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, subscriptionId)
+ .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_RULE_ID, subscriptionRuleId)
+ .putExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, dimensionsValue);
+ try {
+ intentSender.sendIntent(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null);
+ } catch (IntentSender.SendIntentException e) {
+ Slog.w(TAG, "Unable to send using IntentSender from uid " + configUid
+ + "; presumably it had been cancelled.");
+ if (DEBUG) {
+ Slog.d(TAG, String.format("SubscriberBroadcast params {%d %d %d %d %s}",
+ configUid, configKey, subscriptionId,
+ subscriptionRuleId, dimensionsValue));
+ }
+ }
+ }
+
private final static int[] toIntArray(List<Integer> list) {
int[] ret = new int[list.size()];
for (int i = 0; i < ret.length; i++) {
@@ -673,6 +706,8 @@
enforceCallingPermission();
if (DEBUG) Slog.d(TAG, "learned that statsdReady");
sayHiToStatsd(); // tell statsd that we're ready too and link to it
+ mContext.sendBroadcast(new Intent(StatsManager.ACTION_STATSD_STARTED),
+ android.Manifest.permission.DUMP);
}
@Override
diff --git a/services/core/java/com/android/server/utils/AppInstallerUtil.java b/services/core/java/com/android/server/utils/AppInstallerUtil.java
index af7ff41..5d2dbe6 100644
--- a/services/core/java/com/android/server/utils/AppInstallerUtil.java
+++ b/services/core/java/com/android/server/utils/AppInstallerUtil.java
@@ -56,6 +56,7 @@
final Intent result = resolveIntent(context, intent);
if (result != null) {
result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
return result;
}
return null;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index ba5156b..1cfa956 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -238,15 +238,6 @@
return mWin.isAnimating();
}
- /**
- * Is this window currently waiting to run an opening animation?
- */
- boolean isWaitingForOpening() {
- return mService.mAppTransition.isTransitionSet()
- && (mWin.mAppToken != null && mWin.mAppToken.isHidden())
- && mService.mOpeningApps.contains(mWin.mAppToken);
- }
-
void cancelExitAnimationForNextAnimationLocked() {
if (DEBUG_ANIM) Slog.d(TAG,
"cancelExitAnimationForNextAnimationLocked: " + mWin);
@@ -1057,17 +1048,6 @@
return;
}
- // Do not change surface properties of opening apps if we are waiting for the
- // transition to be ready. transitionGoodToGo could be not ready even after all
- // opening apps are drawn. It's only waiting on isFetchingAppTransitionsSpecs()
- // to get the animation spec. (For example, go into Recents and immediately open
- // the same app again before the app's surface is destroyed or saved, the surface
- // is always ready in the whole process.) If we go ahead here, the opening app
- // will be shown with the full size before the correct animation spec arrives.
- if (isWaitingForOpening()) {
- return;
- }
-
boolean displayed = false;
computeShownFrameLocked();
diff --git a/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp b/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp
index 9892146..176ae81 100644
--- a/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp
+++ b/services/core/jni/BroadcastRadio/BroadcastRadioService.cpp
@@ -249,6 +249,15 @@
Tuner::assignHalInterfaces(env, tuner, module.radioModule, halTuner);
ALOGD("Opened tuner %p", halTuner.get());
+
+ bool isConnected = true;
+ halTuner->getConfiguration([&](Result result, const BandConfig& config) {
+ if (result == Result::OK) isConnected = config.antennaConnected;
+ });
+ if (!isConnected) {
+ tunerCb->antennaStateChange(false);
+ }
+
return tuner.release();
}
diff --git a/services/core/jni/BroadcastRadio/Tuner.cpp b/services/core/jni/BroadcastRadio/Tuner.cpp
index 42eb873..42c1332 100644
--- a/services/core/jni/BroadcastRadio/Tuner.cpp
+++ b/services/core/jni/BroadcastRadio/Tuner.cpp
@@ -352,39 +352,6 @@
convert::ThrowIfFailed(env, halTuner->cancelAnnouncement());
}
-static jobject nativeGetProgramInformation(JNIEnv *env, jobject obj, jlong nativeContext) {
- ALOGV("%s", __func__);
- lock_guard<mutex> lk(gContextMutex);
- auto& ctx = getNativeContext(nativeContext);
-
- auto halTuner10 = getHalTuner(ctx);
- auto halTuner11 = ctx.mHalTuner11;
- if (halTuner10 == nullptr) return nullptr;
-
- JavaRef<jobject> jInfo;
- Result halResult;
- Return<void> hidlResult;
- if (halTuner11 != nullptr) {
- hidlResult = halTuner11->getProgramInformation_1_1([&](Result result,
- const V1_1::ProgramInfo& info) {
- halResult = result;
- if (result != Result::OK) return;
- jInfo = convert::ProgramInfoFromHal(env, info);
- });
- } else {
- hidlResult = halTuner10->getProgramInformation([&](Result result,
- const V1_0::ProgramInfo& info) {
- halResult = result;
- if (result != Result::OK) return;
- jInfo = convert::ProgramInfoFromHal(env, info, ctx.mBand);
- });
- }
-
- if (jInfo != nullptr) return jInfo.release();
- convert::ThrowIfFailed(env, hidlResult, halResult);
- return nullptr;
-}
-
static bool nativeStartBackgroundScan(JNIEnv *env, jobject obj, jlong nativeContext) {
ALOGV("%s", __func__);
auto halTuner = getHalTuner11(nativeContext);
@@ -541,21 +508,6 @@
return jResults.release();
}
-static bool nativeIsAntennaConnected(JNIEnv *env, jobject obj, jlong nativeContext) {
- ALOGV("%s", __func__);
- auto halTuner = getHalTuner(nativeContext);
- if (halTuner == nullptr) return false;
-
- bool isConnected = false;
- Result halResult;
- auto hidlResult = halTuner->getConfiguration([&](Result result, const BandConfig& config) {
- halResult = result;
- isConnected = config.antennaConnected;
- });
- convert::ThrowIfFailed(env, hidlResult, halResult);
- return isConnected;
-}
-
static const JNINativeMethod gTunerMethods[] = {
{ "nativeInit", "(IZI)J", (void*)nativeInit },
{ "nativeFinalize", "(J)V", (void*)nativeFinalize },
@@ -570,8 +522,6 @@
{ "nativeTune", "(JLandroid/hardware/radio/ProgramSelector;)V", (void*)nativeTune },
{ "nativeCancel", "(J)V", (void*)nativeCancel },
{ "nativeCancelAnnouncement", "(J)V", (void*)nativeCancelAnnouncement },
- { "nativeGetProgramInformation", "(J)Landroid/hardware/radio/RadioManager$ProgramInfo;",
- (void*)nativeGetProgramInformation },
{ "nativeStartBackgroundScan", "(J)Z", (void*)nativeStartBackgroundScan },
{ "nativeGetProgramList", "(JLjava/util/Map;)Ljava/util/List;",
(void*)nativeGetProgramList },
@@ -580,7 +530,6 @@
{ "nativeSetAnalogForced", "(JZ)V", (void*)nativeSetAnalogForced },
{ "nativeSetParameters", "(JLjava/util/Map;)Ljava/util/Map;", (void*)nativeSetParameters },
{ "nativeGetParameters", "(JLjava/util/List;)Ljava/util/Map;", (void*)nativeGetParameters },
- { "nativeIsAntennaConnected", "(J)Z", (void*)nativeIsAntennaConnected },
};
} // namespace Tuner
diff --git a/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/services/net/java/android/net/util/MultinetworkPolicyTracker.java
index 424e40d..30c5cd9 100644
--- a/services/net/java/android/net/util/MultinetworkPolicyTracker.java
+++ b/services/net/java/android/net/util/MultinetworkPolicyTracker.java
@@ -122,6 +122,7 @@
return mAvoidBadWifi;
}
+ // TODO: move this to MultipathPolicyTracker.
public int getMeteredMultipathPreference() {
return mMeteredMultipathPreference;
}
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index ae311f8..d825533 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -13,9 +13,9 @@
# limitations under the License.
-############################################################
-# FrameworksServicesLib app just for Robolectric test target. #
-############################################################
+##############################################################
+# FrameworksServicesLib app just for Robolectric test target #
+##############################################################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
@@ -31,14 +31,43 @@
include $(BUILD_PACKAGE)
-#############################################
-# FrameworksServices Robolectric test target. #
-#############################################
+##############################################
+# FrameworksServices Robolectric test target #
+##############################################
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+# Dependency platform-robolectric-android-all-stubs below contains a bunch of Android classes as
+# stubs that throw RuntimeExceptions when we use them. The goal is to include hidden APIs that
+# weren't included in Robolectric's Android jar files. However, we are testing the framework itself
+# here, so if we write stuff that is being used in the tests and exist in
+# platform-robolectric-android-all-stubs, the class loader is going to pick up the latter, and thus
+# we are going to test what we don't want. To solve this:
+#
+# 1. If the class being used should be visible to bundled apps:
+# => Bypass the stubs target by including them in LOCAL_SRC_FILES and LOCAL_AIDL_INCLUDES
+# (if aidl).
+#
+# 2. If it's not visible:
+# => Remove the class from the stubs jar (common/robolectric/android-all/android-all-stubs.jar)
+# and add the class path to
+# common/robolectric/android-all/android-all-stubs_removed_classes.txt.
+#
-# Include the testing libraries (JUnit4 + Robolectric libs).
+INTERNAL_BACKUP := ../../core/java/com/android/internal/backup
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \
+ ../../core/java/android/content/pm/PackageInfo.java \
+ ../../core/java/android/app/backup/BackupAgent.java \
+ ../../core/java/android/app/backup/BackupDataOutput.java \
+ ../../core/java/android/app/backup/FullBackupDataOutput.java \
+ ../../core/java/android/app/IBackupAgent.aidl
+
+LOCAL_AIDL_INCLUDES := \
+ $(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \
+ ../../core/java/android/app/IBackupAgent.aidl
+
LOCAL_STATIC_JAVA_LIBRARIES := \
platform-robolectric-android-all-stubs \
android-support-test \
@@ -58,9 +87,9 @@
include $(BUILD_STATIC_JAVA_LIBRARY)
-#############################################################
-# FrameworksServices runner target to run the previous target. #
-#############################################################
+###############################################################
+# FrameworksServices runner target to run the previous target #
+###############################################################
include $(CLEAR_VARS)
LOCAL_MODULE := RunFrameworksServicesRoboTests
diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
new file mode 100644
index 0000000..3668350
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup;
+
+import static com.android.server.backup.testing.TransportData.backupTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransport;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import static java.util.Collections.emptyList;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toList;
+
+import android.app.Application;
+import android.app.IActivityManager;
+import android.app.IBackupAgent;
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.FullBackupDataOutput;
+import android.app.backup.IBackupManager;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IBackupObserver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.internal.BackupHandler;
+import com.android.server.backup.internal.BackupRequest;
+import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.internal.PerformBackupTask;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderClasses;
+import com.android.server.testing.shadows.FrameworkShadowPackageManager;
+import com.android.server.testing.shadows.ShadowBackupDataInput;
+import com.android.server.testing.shadows.ShadowBackupDataOutput;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.shadows.ShadowQueuedWork;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(
+ manifest = Config.NONE,
+ sdk = 26,
+ shadows = {
+ FrameworkShadowPackageManager.class,
+ ShadowBackupDataInput.class,
+ ShadowBackupDataOutput.class,
+ ShadowQueuedWork.class
+ }
+)
+@SystemLoaderClasses({
+ PerformBackupTask.class,
+ BackupDataOutput.class,
+ FullBackupDataOutput.class,
+ TransportManager.class,
+ BackupAgent.class,
+ IBackupTransport.class,
+ IBackupAgent.class,
+ PackageInfo.class
+})
+@Presubmit
+public class PerformBackupTaskTest {
+ private static final String PACKAGE_1 = "com.example.package1";
+ private static final String PACKAGE_2 = "com.example.package2";
+
+ @Mock private RefactoredBackupManagerService mBackupManagerService;
+ @Mock private TransportManager mTransportManager;
+ @Mock private DataChangedJournal mDataChangedJournal;
+ @Mock private IBackupObserver mObserver;
+ @Mock private IBackupManagerMonitor mMonitor;
+ @Mock private OnTaskFinishedListener mListener;
+ private TransportData mTransport;
+ private IBackupTransport mTransportBinder;
+ private TransportClient mTransportClient;
+ private ShadowLooper mShadowBackupLooper;
+ private BackupHandler mBackupHandler;
+ private PowerManager.WakeLock mWakeLock;
+ private ShadowPackageManager mShadowPackageManager;
+ private FakeIBackupManager mBackupManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mTransport = backupTransport();
+ TransportMock transportMock = setUpTransport(mTransportManager, mTransport);
+ mTransportBinder = transportMock.transport;
+ mTransportClient = transportMock.transportClient;
+
+ Application application = RuntimeEnvironment.application;
+ File cacheDir = application.getCacheDir();
+ File baseStateDir = new File(cacheDir, "base_state_dir");
+ File dataDir = new File(cacheDir, "data_dir");
+ File stateDir = new File(baseStateDir, mTransport.transportDirName);
+ assertThat(baseStateDir.mkdir()).isTrue();
+ assertThat(dataDir.mkdir()).isTrue();
+ assertThat(stateDir.mkdir()).isTrue();
+
+ PackageManager packageManager = application.getPackageManager();
+ mShadowPackageManager = Shadow.extract(packageManager);
+
+ PowerManager powerManager =
+ (PowerManager) application.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
+
+ // Robolectric simulates multi-thread in a single-thread to avoid flakiness
+ HandlerThread backupThread = new HandlerThread("backup");
+ backupThread.setUncaughtExceptionHandler(
+ (t, e) -> fail("Uncaught exception " + e.getMessage()));
+ backupThread.start();
+ Looper backupLooper = backupThread.getLooper();
+ mShadowBackupLooper = shadowOf(backupLooper);
+ mBackupHandler = new BackupHandler(mBackupManagerService, backupLooper);
+
+ mBackupManager = spy(FakeIBackupManager.class);
+
+ when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager);
+ when(mBackupManagerService.getContext()).thenReturn(application);
+ when(mBackupManagerService.getPackageManager()).thenReturn(packageManager);
+ when(mBackupManagerService.getWakelock()).thenReturn(mWakeLock);
+ when(mBackupManagerService.getCurrentOpLock()).thenReturn(new Object());
+ when(mBackupManagerService.getQueueLock()).thenReturn(new Object());
+ when(mBackupManagerService.getBaseStateDir()).thenReturn(baseStateDir);
+ when(mBackupManagerService.getDataDir()).thenReturn(dataDir);
+ when(mBackupManagerService.getCurrentOperations()).thenReturn(new SparseArray<>());
+ when(mBackupManagerService.getBackupHandler()).thenReturn(mBackupHandler);
+ when(mBackupManagerService.getBackupManagerBinder()).thenReturn(mBackupManager);
+ when(mBackupManagerService.getActivityManager()).thenReturn(mock(IActivityManager.class));
+ }
+
+ @Test
+ public void testRunTask_whenTransportProvidesFlags_passesThemToTheAgent() throws Exception {
+ BackupAgent agent = setUpAgent(PACKAGE_1);
+ int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+ when(mTransportBinder.getTransportFlags()).thenReturn(flags);
+ PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+
+ runTask(task);
+
+ verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+ }
+
+ @Test
+ public void testRunTask_whenTransportDoesNotProvidesFlags() throws Exception {
+ BackupAgent agent = setUpAgent(PACKAGE_1);
+ PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+
+ runTask(task);
+
+ verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(0)), any());
+ }
+
+ @Test
+ public void testRunTask_whenTransportProvidesFlagsAndMultipleAgents_passesToAll()
+ throws Exception {
+ List<BackupAgent> agents = setUpAgents(PACKAGE_1, PACKAGE_2);
+ BackupAgent agent1 = agents.get(0);
+ BackupAgent agent2 = agents.get(1);
+ int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+ when(mTransportBinder.getTransportFlags()).thenReturn(flags);
+ PerformBackupTask task =
+ createPerformBackupTask(emptyList(), false, true, PACKAGE_1, PACKAGE_2);
+
+ runTask(task);
+
+ verify(agent1).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+ verify(agent2).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+ }
+
+ @Test
+ public void testRunTask_whenTransportChangeFlagsAfterTaskCreation() throws Exception {
+ BackupAgent agent = setUpAgent(PACKAGE_1);
+ PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+ int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+ when(mTransportBinder.getTransportFlags()).thenReturn(flags);
+
+ runTask(task);
+
+ verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+ }
+
+ private void runTask(PerformBackupTask task) {
+ Message message = mBackupHandler.obtainMessage(BackupHandler.MSG_BACKUP_RESTORE_STEP, task);
+ mBackupHandler.sendMessage(message);
+ while (mShadowBackupLooper.getScheduler().areAnyRunnable()) {
+ mShadowBackupLooper.runToEndOfTasks();
+ }
+ }
+
+ private List<BackupAgent> setUpAgents(String... packageNames) {
+ return Stream.of(packageNames).map(this::setUpAgent).collect(toList());
+ }
+
+ private BackupAgent setUpAgent(String packageName) {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags = ApplicationInfo.FLAG_ALLOW_BACKUP;
+ packageInfo.applicationInfo.backupAgentName = "BackupAgent" + packageName;
+ packageInfo.applicationInfo.packageName = packageName;
+ mShadowPackageManager.setApplicationEnabledSetting(
+ packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ mShadowPackageManager.addPackage(packageInfo);
+ BackupAgent backupAgent = spy(BackupAgent.class);
+ IBackupAgent backupAgentBinder = IBackupAgent.Stub.asInterface(backupAgent.onBind());
+ when(mBackupManagerService.bindToAgentSynchronous(
+ eq(packageInfo.applicationInfo), anyInt()))
+ .thenReturn(backupAgentBinder);
+ return backupAgent;
+ }
+
+ private PerformBackupTask createPerformBackupTask(
+ List<String> pendingFullBackups,
+ boolean userInitiated,
+ boolean nonIncremental,
+ String... packages) {
+ ArrayList<BackupRequest> backupRequests =
+ Stream.of(packages).map(BackupRequest::new).collect(toCollection(ArrayList::new));
+ mWakeLock.acquire();
+ PerformBackupTask task =
+ new PerformBackupTask(
+ mBackupManagerService,
+ mTransportClient,
+ mTransport.transportDirName,
+ backupRequests,
+ mDataChangedJournal,
+ mObserver,
+ mMonitor,
+ mListener,
+ pendingFullBackups,
+ userInitiated,
+ nonIncremental);
+ mBackupManager.setUp(mBackupHandler, task);
+ return task;
+ }
+
+ private ArgumentMatcher<BackupDataOutput> dataOutputWithTransportFlags(int flags) {
+ return dataOutput -> dataOutput.getTransportFlags() == flags;
+ }
+
+ private abstract static class FakeIBackupManager extends IBackupManager.Stub {
+ private Handler mBackupHandler;
+ private BackupRestoreTask mTask;
+
+ public FakeIBackupManager() {}
+
+ private void setUp(Handler backupHandler, BackupRestoreTask task) {
+ mBackupHandler = backupHandler;
+ mTask = task;
+ }
+
+ @Override
+ public void opComplete(int token, long result) throws RemoteException {
+ assertThat(mTask).isNotNull();
+ Message message =
+ mBackupHandler.obtainMessage(
+ BackupHandler.MSG_OP_COMPLETE, Pair.create(mTask, result));
+ mBackupHandler.sendMessage(message);
+ }
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/src/com/android/server/backup/testing/TestUtils.java
index 1be298d..407a1bc 100644
--- a/services/robotests/src/com/android/server/backup/testing/TestUtils.java
+++ b/services/robotests/src/com/android/server/backup/testing/TestUtils.java
@@ -16,11 +16,28 @@
package com.android.server.backup.testing;
+import static com.google.common.truth.Truth.assertThat;
+
+
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import org.robolectric.shadows.ShadowLog;
+
import java.util.concurrent.Callable;
public class TestUtils {
+ /** Reset logcat with {@link ShadowLog#reset()} before the test case */
+ public static void assertLogcatAtMost(String tag, int level) {
+ assertThat(ShadowLog.getLogsForTag(tag).stream().allMatch(logItem -> logItem.type <= level))
+ .isTrue();
+ }
+
+ /** Reset logcat with {@link ShadowLog#reset()} before the test case */
+ public static void assertLogcatAtLeast(String tag, int level) {
+ assertThat(ShadowLog.getLogsForTag(tag).stream().anyMatch(logItem -> logItem.type >= level))
+ .isTrue();
+ }
+
/**
* Calls {@link Runnable#run()} and returns if no exception is thrown. Otherwise, if the
* exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} and
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
index e1dc7b5e..565c7e6 100644
--- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
+++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
@@ -144,12 +144,14 @@
// Transport registered and available
IBackupTransport transportMock = mockTransportBinder(transport);
when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
+ when(transportClientMock.connect(any())).thenReturn(transportMock);
return new TransportMock(transportClientMock, transportMock);
} else {
// Transport registered but unavailable
when(transportClientMock.connectOrThrow(any()))
.thenThrow(TransportNotAvailableException.class);
+ when(transportClientMock.connect(any())).thenReturn(null);
return new TransportMock(transportClientMock, null);
}
diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
index 10442b7..db6e62f 100644
--- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
+++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
@@ -17,7 +17,11 @@
package com.android.server.backup.transport;
import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+import static com.android.server.backup.testing.TestUtils.assertLogcatAtLeast;
+import static com.android.server.backup.testing.TestUtils.assertLogcatAtMost;
+
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -25,6 +29,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
+import static org.testng.Assert.expectThrows;
import android.content.ComponentName;
import android.content.Context;
@@ -34,12 +39,17 @@
import android.os.Looper;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
import com.android.server.backup.TransportManager;
import com.android.server.testing.FrameworkRobolectricTestRunner;
-import com.android.server.testing.ShadowEventLog;
import com.android.server.testing.SystemLoaderClasses;
+import com.android.server.testing.shadows.ShadowCloseGuard;
+import com.android.server.testing.shadows.ShadowEventLog;
+import com.android.server.testing.shadows.ShadowSlog;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,10 +57,15 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowLooper;
@RunWith(FrameworkRobolectricTestRunner.class)
-@Config(manifest = Config.NONE, sdk = 26, shadows = {ShadowEventLog.class})
+@Config(
+ manifest = Config.NONE,
+ sdk = 26,
+ shadows = {ShadowEventLog.class, ShadowCloseGuard.class, ShadowSlog.class}
+)
@SystemLoaderClasses({TransportManager.class, TransportClient.class})
@Presubmit
public class TransportClientTest {
@@ -59,7 +74,7 @@
@Mock private Context mContext;
@Mock private TransportConnectionListener mTransportConnectionListener;
@Mock private TransportConnectionListener mTransportConnectionListener2;
- @Mock private IBackupTransport.Stub mIBackupTransport;
+ @Mock private IBackupTransport.Stub mTransportBinder;
private TransportClient mTransportClient;
private ComponentName mTransportComponent;
private String mTransportString;
@@ -78,7 +93,12 @@
mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent);
mTransportClient =
new TransportClient(
- mContext, mBindIntent, mTransportComponent, "1", new Handler(mainLooper));
+ mContext,
+ mBindIntent,
+ mTransportComponent,
+ "1",
+ "caller",
+ new Handler(mainLooper));
when(mContext.bindServiceAsUser(
eq(mBindIntent),
@@ -111,7 +131,7 @@
// Simulate framework connecting
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
- connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
mShadowLooper.runToEndOfTasks();
verify(mTransportConnectionListener)
@@ -126,7 +146,7 @@
mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
- connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
mShadowLooper.runToEndOfTasks();
verify(mTransportConnectionListener)
@@ -139,7 +159,7 @@
public void testConnectAsync_whenAlreadyConnected_callsListener() throws Exception {
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
- connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
@@ -180,11 +200,11 @@
}
@Test
- public void testConnectAsync_afterServiceDisconnectedBeforeNewConnection_callsListener()
+ public void testConnectAsync_afterOnServiceDisconnectedBeforeNewConnection_callsListener()
throws Exception {
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
- connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
connection.onServiceDisconnected(mTransportComponent);
mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
@@ -194,13 +214,13 @@
}
@Test
- public void testConnectAsync_afterServiceDisconnectedAfterNewConnection_callsListener()
+ public void testConnectAsync_afterOnServiceDisconnectedAfterNewConnection_callsListener()
throws Exception {
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
- connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
connection.onServiceDisconnected(mTransportComponent);
- connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
@@ -240,7 +260,7 @@
@Test
public void testConnectAsync_beforeFrameworkCall_logsBoundTransition() {
- ShadowEventLog.clearEvents();
+ ShadowEventLog.setUp();
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
@@ -249,11 +269,11 @@
@Test
public void testConnectAsync_afterOnServiceConnected_logsBoundAndConnectedTransitions() {
- ShadowEventLog.clearEvents();
+ ShadowEventLog.setUp();
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
- connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 1);
assertEventLogged(EventLogTags.BACKUP_TRANSPORT_CONNECTION, mTransportString, 1);
@@ -261,7 +281,7 @@
@Test
public void testConnectAsync_afterOnBindingDied_logsBoundAndUnboundTransitions() {
- ShadowEventLog.clearEvents();
+ ShadowEventLog.setUp();
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
@@ -275,8 +295,8 @@
public void testUnbind_whenConnected_logsDisconnectedAndUnboundTransitions() {
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
- connection.onServiceConnected(mTransportComponent, mIBackupTransport);
- ShadowEventLog.clearEvents();
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
+ ShadowEventLog.setUp();
mTransportClient.unbind("caller1");
@@ -288,8 +308,8 @@
public void testOnServiceDisconnected_whenConnected_logsDisconnectedAndUnboundTransitions() {
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
- connection.onServiceConnected(mTransportComponent, mIBackupTransport);
- ShadowEventLog.clearEvents();
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
+ ShadowEventLog.setUp();
connection.onServiceDisconnected(mTransportComponent);
@@ -301,8 +321,8 @@
public void testOnBindingDied_whenConnected_logsDisconnectedAndUnboundTransitions() {
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
- connection.onServiceConnected(mTransportComponent, mIBackupTransport);
- ShadowEventLog.clearEvents();
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
+ ShadowEventLog.setUp();
connection.onBindingDied(mTransportComponent);
@@ -310,6 +330,122 @@
assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0);
}
+ @Test
+ public void testMarkAsDisposed_whenCreated() throws Throwable {
+ mTransportClient.markAsDisposed();
+
+ // No exception thrown
+ }
+
+ @Test
+ public void testMarkAsDisposed_afterOnBindingDied() throws Throwable {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onBindingDied(mTransportComponent);
+
+ mTransportClient.markAsDisposed();
+
+ // No exception thrown
+ }
+
+ @Test
+ public void testMarkAsDisposed_whenConnectedAndUnbound() throws Throwable {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
+ mTransportClient.unbind("caller1");
+
+ mTransportClient.markAsDisposed();
+
+ // No exception thrown
+ }
+
+ @Test
+ public void testMarkAsDisposed_afterOnServiceDisconnected() throws Throwable {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
+ connection.onServiceDisconnected(mTransportComponent);
+
+ mTransportClient.markAsDisposed();
+
+ // No exception thrown
+ }
+
+ @Test
+ public void testMarkAsDisposed_whenBound() throws Throwable {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+
+ expectThrows(RuntimeException.class, mTransportClient::markAsDisposed);
+ }
+
+ @Test
+ public void testMarkAsDisposed_whenConnected() throws Throwable {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
+
+ expectThrows(RuntimeException.class, mTransportClient::markAsDisposed);
+ }
+
+ @Test
+ @SuppressWarnings("FinalizeCalledExplicitly")
+ public void testFinalize_afterCreated() throws Throwable {
+ ShadowLog.reset();
+
+ mTransportClient.finalize();
+
+ assertLogcatAtMost(TransportClient.TAG, Log.INFO);
+ }
+
+ @Test
+ @SuppressWarnings("FinalizeCalledExplicitly")
+ public void testFinalize_whenBound() throws Throwable {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ShadowLog.reset();
+
+ mTransportClient.finalize();
+
+ assertLogcatAtLeast(TransportClient.TAG, Log.ERROR);
+ }
+
+ @Test
+ @SuppressWarnings("FinalizeCalledExplicitly")
+ public void testFinalize_whenConnected() throws Throwable {
+ mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+ ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+ connection.onServiceConnected(mTransportComponent, mTransportBinder);
+ ShadowLog.reset();
+
+ mTransportClient.finalize();
+
+ expectThrows(
+ TransportNotAvailableException.class,
+ () -> mTransportClient.getConnectedTransport("caller1"));
+ assertLogcatAtLeast(TransportClient.TAG, Log.ERROR);
+ }
+
+ @Test
+ @SuppressWarnings("FinalizeCalledExplicitly")
+ public void testFinalize_whenNotMarkedAsDisposed() throws Throwable {
+ ShadowCloseGuard.setUp();
+
+ mTransportClient.finalize();
+
+ assertThat(ShadowCloseGuard.hasReported()).isTrue();
+ }
+
+ @Test
+ @SuppressWarnings("FinalizeCalledExplicitly")
+ public void testFinalize_whenMarkedAsDisposed() throws Throwable {
+ mTransportClient.markAsDisposed();
+ ShadowCloseGuard.setUp();
+
+ mTransportClient.finalize();
+
+ assertThat(ShadowCloseGuard.hasReported()).isFalse();
+ }
+
private void assertEventLogged(int tag, Object... values) {
assertThat(ShadowEventLog.hasEvent(tag, values)).isTrue();
}
diff --git a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
index 6c7313b..c94d598 100644
--- a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
+++ b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
@@ -16,6 +16,9 @@
package com.android.server.testing;
+import com.android.server.backup.PerformBackupTaskTest;
+import com.android.server.backup.internal.PerformBackupTask;
+
import com.google.common.collect.ImmutableSet;
import org.junit.runners.model.FrameworkMethod;
@@ -30,6 +33,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Enumeration;
import java.util.Set;
import javax.annotation.Nonnull;
@@ -115,6 +121,27 @@
}
/**
+ * HACK^2
+ * The framework Robolectric run configuration puts a prebuilt in front of us, so we try not
+ * to load the class from there, if possible.
+ */
+ @Override
+ public InputStream getResourceAsStream(String resource) {
+ try {
+ Enumeration<URL> urls = getResources(resource);
+ while (urls.hasMoreElements()) {
+ URL url = urls.nextElement();
+ if (!url.toString().toLowerCase().contains("prebuilt")) {
+ return url.openStream();
+ }
+ }
+ } catch (IOException e) {
+ // Fall through
+ }
+ return super.getResourceAsStream(resource);
+ }
+
+ /**
* Classes like com.package.ClassName$InnerClass should also be loaded from the system class
* loader, so we test if the classes in the annotation are prefixes of the class to load.
*/
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java
new file mode 100644
index 0000000..28489af
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.testing.shadows;
+
+import android.app.backup.BackupDataInput;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+@Implements(BackupDataInput.class)
+public class ShadowBackupDataInput {
+ @Implementation
+ public void __constructor__(FileDescriptor fd) {
+ }
+
+ @Implementation
+ protected void finalize() throws Throwable {
+ }
+
+ @Implementation
+ public boolean readNextHeader() throws IOException {
+ return false;
+ }
+
+ @Implementation
+ public String getKey() {
+ throw new AssertionError("Can't call because readNextHeader() returned false");
+ }
+
+ @Implementation
+ public int getDataSize() {
+ throw new AssertionError("Can't call because readNextHeader() returned false");
+ }
+
+ @Implementation
+ public int readEntityData(byte[] data, int offset, int size) throws IOException {
+ throw new AssertionError("Can't call because readNextHeader() returned false");
+ }
+
+ @Implementation
+ public void skipEntityData() throws IOException {
+ throw new AssertionError("Can't call because readNextHeader() returned false");
+ }
+}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java
new file mode 100644
index 0000000..c7deada
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.testing.shadows;
+
+import android.app.backup.BackupDataOutput;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+@Implements(BackupDataOutput.class)
+public class ShadowBackupDataOutput {
+ private long mQuota;
+ private int mTransportFlags;
+
+ @Implementation
+ public void __constructor__(FileDescriptor fd, long quota, int transportFlags) {
+ mQuota = quota;
+ mTransportFlags = transportFlags;
+ }
+
+ @Implementation
+ public long getQuota() {
+ return mQuota;
+ }
+
+ @Implementation
+ public int getTransportFlags() {
+ return mTransportFlags;
+ }
+
+ @Implementation
+ public int writeEntityHeader(String key, int dataSize) throws IOException {
+ return 0;
+ }
+
+ @Implementation
+ public int writeEntityData(byte[] data, int size) throws IOException {
+ return 0;
+ }
+}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowCloseGuard.java b/services/robotests/src/com/android/server/testing/shadows/ShadowCloseGuard.java
new file mode 100644
index 0000000..c9984bf
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowCloseGuard.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.testing.shadows;
+
+import dalvik.system.CloseGuard;
+
+import org.robolectric.annotation.Implements;
+
+@Implements(CloseGuard.class)
+public class ShadowCloseGuard {
+ private static final Reporter REPORTER = new Reporter();
+
+ public static boolean hasReported() {
+ return REPORTER.mReports > 0;
+ }
+
+ public static void setUp() {
+ // Can't do this in static {} block because shadow initialization is part of real class
+ // initialization and it happens right in the beginning. When the shadow is being
+ // initialized the class hasn't been initialized yet and it will be after the shadow. So,
+ // REPORTER field (inside CloseGuard) will be assigned *after* setReporter() is called.
+ CloseGuard.setReporter(REPORTER);
+ REPORTER.mReports = 0;
+ }
+
+ private static class Reporter implements CloseGuard.Reporter {
+ private int mReports = 0;
+
+ @Override
+ public void report(String message, Throwable allocationSite) {
+ mReports += 1;
+ }
+ }
+}
diff --git a/services/robotests/src/com/android/server/testing/ShadowEventLog.java b/services/robotests/src/com/android/server/testing/shadows/ShadowEventLog.java
similarity index 94%
rename from services/robotests/src/com/android/server/testing/ShadowEventLog.java
rename to services/robotests/src/com/android/server/testing/shadows/ShadowEventLog.java
index b8059f4..4625684 100644
--- a/services/robotests/src/com/android/server/testing/ShadowEventLog.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowEventLog.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.server.testing;
+package com.android.server.testing.shadows;
import android.util.EventLog;
@@ -40,7 +40,8 @@
return ENTRIES.contains(new Entry(tag, Arrays.asList(values)));
}
- public static void clearEvents() {
+ /** Clears the entries */
+ public static void setUp() {
ENTRIES.clear();
}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowSlog.java b/services/robotests/src/com/android/server/testing/shadows/ShadowSlog.java
new file mode 100644
index 0000000..bf4b61e
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowSlog.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.testing.shadows;
+
+import android.util.Log;
+import android.util.Slog;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowLog;
+
+@Implements(Slog.class)
+public class ShadowSlog {
+ @Implementation
+ public static int println(int priority, String tag, String msg) {
+ return Log.println(priority, tag, msg);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
index a29e169..b68bf2d 100644
--- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
@@ -266,7 +266,7 @@
assertEquals(((restrictionTypes & JOBS_ONLY) != 0),
instance.areJobsRestricted(uid, packageName, exemptFromBatterySaver));
assertEquals(((restrictionTypes & ALARMS_ONLY) != 0),
- instance.areAlarmsRestricted(uid, packageName));
+ instance.areAlarmsRestricted(uid, packageName, exemptFromBatterySaver));
}
private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName,
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index 9e7ef65..fb25cf3 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -281,6 +281,68 @@
assertNull(physical);
}
+ @Test
+ public void testStrategiesAdaptToUserDataPoint() {
+ Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
+ DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
+ assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res));
+ res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
+ assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res));
+ }
+
+ private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) {
+ // Save out all of the initial brightness data for comparison after reset.
+ float[] initialBrightnessLevels = new float[LUX_LEVELS.length];
+ for (int i = 0; i < LUX_LEVELS.length; i++) {
+ initialBrightnessLevels[i] = strategy.getBrightness(LUX_LEVELS[i]);
+ }
+
+ // Add a data point in the middle of the curve where the user has set the brightness max
+ final int idx = LUX_LEVELS.length / 2;
+ strategy.addUserDataPoint(LUX_LEVELS[idx], 1.0f);
+
+ // Then make sure that all control points after the middle lux level are also set to max...
+ for (int i = idx; i < LUX_LEVELS.length; i++) {
+ assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.01 /*tolerance*/);
+ }
+
+ // ...and that all control points before the middle lux level are strictly less than the
+ // previous one still.
+ float prevBrightness = strategy.getBrightness(LUX_LEVELS[idx]);
+ for (int i = idx - 1; i >= 0; i--) {
+ float brightness = strategy.getBrightness(LUX_LEVELS[i]);
+ assertTrue("Brightness levels must be monotonic after adapting to user data",
+ prevBrightness >= brightness);
+ prevBrightness = brightness;
+ }
+
+ // Now reset the curve and make sure we go back to the initial brightness levels recorded
+ // before adding the user data point.
+ strategy.clearUserDataPoints();
+ for (int i = 0; i < LUX_LEVELS.length; i++) {
+ assertEquals(initialBrightnessLevels[i], strategy.getBrightness(LUX_LEVELS[i]),
+ 0.01 /*tolerance*/);
+ }
+
+ // Now set the middle of the lux range to something just above the minimum.
+ final float minBrightness = strategy.getBrightness(LUX_LEVELS[0]);
+ strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.01f);
+
+ // Then make sure the curve is still monotonic.
+ prevBrightness = 0f;
+ for (float lux : LUX_LEVELS) {
+ float brightness = strategy.getBrightness(lux);
+ assertTrue("Brightness levels must be monotonic after adapting to user data",
+ prevBrightness <= brightness);
+ prevBrightness = brightness;
+ }
+
+ // And that the lowest lux level still gives the absolute minimum brightness. This should
+ // be true assuming that there are more than two lux levels in the curve since we picked a
+ // brightness just barely above the minimum for the middle of the curve.
+ assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.001 /*tolerance*/);
+ }
+
private static float[] toFloatArray(int[] vals) {
float[] newVals = new float[vals.length];
for (int i = 0; i < vals.length; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 5c7348c..c491601 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -195,7 +195,6 @@
assertEquals(a.installLocation, b.installLocation);
assertEquals(a.coreApp, b.coreApp);
assertEquals(a.mRequiredForAllUsers, b.mRequiredForAllUsers);
- assertEquals(a.mTrustedOverlay, b.mTrustedOverlay);
assertEquals(a.mCompileSdkVersion, b.mCompileSdkVersion);
assertEquals(a.mCompileSdkVersionCodename, b.mCompileSdkVersionCodename);
assertEquals(a.use32bitAbi, b.use32bitAbi);
@@ -429,7 +428,6 @@
pkg.installLocation = 100;
pkg.coreApp = true;
pkg.mRequiredForAllUsers = true;
- pkg.mTrustedOverlay = true;
pkg.use32bitAbi = true;
pkg.packageName = "foo";
pkg.splitNames = new String[] { "foo2" };
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 0cd9ac3..feb7b76 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -10,5 +10,6 @@
static_libs: [
"android.hardware.usb-V1.0-java",
"android.hardware.usb-V1.1-java",
+ "android.hardware.usb.gadget-V1.0-java",
],
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 4a7072d..e3e5e3e 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -41,13 +41,21 @@
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
+import android.hardware.usb.gadget.V1_0.GadgetFunction;
+import android.hardware.usb.gadget.V1_0.IUsbGadget;
+import android.hardware.usb.gadget.V1_0.IUsbGadgetCallback;
+import android.hardware.usb.gadget.V1_0.Status;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
import android.os.BatteryManager;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.HwBinder;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UEventObserver;
@@ -75,8 +83,10 @@
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
+import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.Set;
+import java.util.StringJoiner;
/**
* UsbDeviceManager manages USB state in device mode.
@@ -87,22 +97,6 @@
private static final boolean DEBUG = false;
/**
- * The persistent property which stores whether adb is enabled or not.
- * May also contain vendor-specific default functions for testing purposes.
- */
- private static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config";
-
- /**
- * The non-persistent property which stores the current USB settings.
- */
- private static final String USB_CONFIG_PROPERTY = "sys.usb.config";
-
- /**
- * The non-persistent property which stores the current USB actual state.
- */
- private static final String USB_STATE_PROPERTY = "sys.usb.state";
-
- /**
* The SharedPreference setting per user that stores the screen unlocked functions between
* sessions.
*/
@@ -142,6 +136,10 @@
private static final int MSG_LOCALE_CHANGED = 11;
private static final int MSG_SET_SCREEN_UNLOCKED_FUNCTIONS = 12;
private static final int MSG_UPDATE_SCREEN_LOCK = 13;
+ private static final int MSG_SET_CHARGING_FUNCTIONS = 14;
+ private static final int MSG_SET_FUNCTIONS_TIMEOUT = 15;
+ private static final int MSG_GET_CURRENT_USB_FUNCTIONS = 16;
+ private static final int MSG_FUNCTION_SWITCH_TIMEOUT = 17;
private static final int AUDIO_MODE_SOURCE = 1;
@@ -157,9 +155,9 @@
private static final String BOOT_MODE_PROPERTY = "ro.bootmode";
private static final String ADB_NOTIFICATION_CHANNEL_ID_TV = "usbdevicemanager.adb.tv";
-
private UsbHandler mHandler;
private boolean mBootCompleted;
+ private boolean mSystemReady;
private final Object mLock = new Object();
@@ -175,7 +173,6 @@
private boolean mMidiEnabled;
private int mMidiCard;
private int mMidiDevice;
- private HashMap<String, HashMap<String, Pair<String, String>>> mOemModeMap;
private String[] mAccessoryStrings;
private UsbDebuggingManager mDebuggingManager;
private final UsbAlsaManager mUsbAlsaManager;
@@ -267,9 +264,27 @@
mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
initRndisAddress();
- readOemUsbOverrideConfig();
+ boolean halNotPresent = false;
+ try {
+ IUsbGadget.getService(true);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "USB GADGET HAL present but exception thrown", e);
+ } catch (NoSuchElementException e) {
+ halNotPresent = true;
+ Slog.i(TAG, "USB GADGET HAL not present in the device", e);
+ }
- mHandler = new UsbHandler(FgThread.get().getLooper());
+ if (halNotPresent) {
+ /**
+ * Initialze the legacy UsbHandler
+ */
+ mHandler = new UsbHandlerLegacy(FgThread.get().getLooper(), mContext);
+ } else {
+ /**
+ * Initialize HAL based UsbHandler
+ */
+ mHandler = new UsbHandlerHal(FgThread.get().getLooper());
+ }
if (nativeIsStartRequested()) {
if (DEBUG) Slog.d(TAG, "accessory attached at boot");
@@ -367,15 +382,6 @@
massStorageSupported = primary != null && primary.allowMassStorage();
mUseUsbNotification = !massStorageSupported && mContext.getResources().getBoolean(
com.android.internal.R.bool.config_usbChargingMessage);
-
- // make sure the ADB_ENABLED setting value matches the current state
- try {
- Settings.Global.putInt(mContentResolver,
- Settings.Global.ADB_ENABLED, mAdbEnabled ? 1 : 0);
- } catch (SecurityException e) {
- // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't be changed.
- Slog.d(TAG, "ADB_ENABLED is restricted.");
- }
mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
}
@@ -457,7 +463,7 @@
return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
}
- private final class UsbHandler extends Handler {
+ private abstract class UsbHandler extends Handler {
// current USB state
private boolean mConnected;
@@ -465,78 +471,53 @@
private boolean mSourcePower;
private boolean mSinkPower;
private boolean mConfigured;
- private boolean mUsbDataUnlocked;
+ protected boolean mUsbDataUnlocked;
private boolean mAudioAccessoryConnected;
private boolean mAudioAccessorySupported;
- private String mCurrentFunctions;
- private boolean mCurrentFunctionsApplied;
+ protected String mCurrentFunctions;
+ protected boolean mCurrentFunctionsApplied;
private UsbAccessory mCurrentAccessory;
private int mUsbNotificationId;
private boolean mAdbNotificationShown;
private int mCurrentUser;
private boolean mUsbCharging;
- private String mCurrentOemFunctions;
private boolean mHideUsbNotification;
private boolean mSupportsAllCombinations;
private String mScreenUnlockedFunctions = UsbManager.USB_FUNCTION_NONE;
private boolean mScreenLocked;
+ protected boolean mCurrentUsbFunctionsRequested;
+ protected boolean mCurrentUsbFunctionsReceived;
+
+ /**
+ * The persistent property which stores whether adb is enabled or not.
+ * May also contain vendor-specific default functions for testing purposes.
+ */
+ protected static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config";
public UsbHandler(Looper looper) {
super(looper);
- try {
- // Restore default functions.
- mCurrentOemFunctions = SystemProperties.get(UsbDeviceManager.getPersistProp(false),
- UsbManager.USB_FUNCTION_NONE);
- if (isNormalBoot()) {
- mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY,
- UsbManager.USB_FUNCTION_NONE);
- mCurrentFunctionsApplied = mCurrentFunctions.equals(
- SystemProperties.get(USB_STATE_PROPERTY));
- } else {
- mCurrentFunctions = SystemProperties.get(getPersistProp(true),
- UsbManager.USB_FUNCTION_NONE);
- mCurrentFunctionsApplied = SystemProperties.get(USB_CONFIG_PROPERTY,
- UsbManager.USB_FUNCTION_NONE).equals(
- SystemProperties.get(USB_STATE_PROPERTY));
- }
+ mCurrentUser = ActivityManager.getCurrentUser();
+ mScreenLocked = true;
- mCurrentUser = ActivityManager.getCurrentUser();
- mScreenLocked = true;
+ /*
+ * Use the normal bootmode persistent prop to maintain state of adb across
+ * all boot modes.
+ */
+ mAdbEnabled = UsbManager.containsFunction(
+ SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY),
+ UsbManager.USB_FUNCTION_ADB);
- /*
- * Use the normal bootmode persistent prop to maintain state of adb across
- * all boot modes.
- */
- mAdbEnabled = UsbManager.containsFunction(
- SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY),
- UsbManager.USB_FUNCTION_ADB);
-
- /*
- * Previous versions can set persist config to mtp/ptp but it does not
- * get reset on OTA. Reset the property here instead.
- */
- String persisted = SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY);
- if (UsbManager.containsFunction(persisted, UsbManager.USB_FUNCTION_MTP)
- || UsbManager.containsFunction(persisted, UsbManager.USB_FUNCTION_PTP)) {
- SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY,
- UsbManager.removeFunction(UsbManager.removeFunction(persisted,
- UsbManager.USB_FUNCTION_MTP), UsbManager.USB_FUNCTION_PTP));
- }
-
- String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
- updateState(state);
-
- // register observer to listen for settings changes
- mContentResolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
- false, new AdbSettingsObserver());
-
- // Watch for USB configuration changes
- mUEventObserver.startObserving(USB_STATE_MATCH);
- mUEventObserver.startObserving(ACCESSORY_START_MATCH);
- } catch (Exception e) {
- Slog.e(TAG, "Error initializing UsbHandler", e);
+ /*
+ * Previous versions can set persist config to mtp/ptp but it does not
+ * get reset on OTA. Reset the property here instead.
+ */
+ String persisted = SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY);
+ if (UsbManager.containsFunction(persisted, UsbManager.USB_FUNCTION_MTP)
+ || UsbManager.containsFunction(persisted, UsbManager.USB_FUNCTION_PTP)) {
+ SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY,
+ UsbManager.removeFunction(UsbManager.removeFunction(persisted,
+ UsbManager.USB_FUNCTION_MTP), UsbManager.USB_FUNCTION_PTP));
}
}
@@ -562,6 +543,21 @@
sendMessage(m);
}
+ public void sendMessage(int what, boolean arg1, boolean arg2) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.arg1 = (arg1 ? 1 : 0);
+ m.arg2 = (arg2 ? 1 : 0);
+ sendMessage(m);
+ }
+
+ public void sendMessageDelayed(int what, boolean arg, long delayMillis) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.arg1 = (arg ? 1 : 0);
+ sendMessageDelayed(m, delayMillis);
+ }
+
public void updateState(String state) {
int connected, configured;
@@ -579,6 +575,7 @@
return;
}
removeMessages(MSG_UPDATE_STATE);
+ if (connected == 1) removeMessages(MSG_FUNCTION_SWITCH_TIMEOUT);
Message msg = Message.obtain(this, MSG_UPDATE_STATE);
msg.arg1 = connected;
msg.arg2 = configured;
@@ -601,28 +598,6 @@
sendMessageDelayed(msg, UPDATE_DELAY);
}
- private boolean waitForState(String state) {
- // wait for the transition to complete.
- // give up after 1 second.
- String value = null;
- for (int i = 0; i < 20; i++) {
- // State transition is done when sys.usb.state is set to the new configuration
- value = SystemProperties.get(USB_STATE_PROPERTY);
- if (state.equals(value)) return true;
- SystemClock.sleep(50);
- }
- Slog.e(TAG, "waitForState(" + state + ") FAILED: got " + value);
- return false;
- }
-
- private void setUsbConfig(String config) {
- if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")");
- // set the new configuration
- // we always set it due to b/23631400, where adbd was getting killed
- // and not restarted due to property timeouts on some devices
- SystemProperties.set(USB_CONFIG_PROPERTY, config);
- }
-
private void setAdbEnabled(boolean enable) {
if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
if (enable != mAdbEnabled) {
@@ -649,114 +624,7 @@
}
}
- /**
- * Evaluates USB function policies and applies the change accordingly.
- */
- private void setEnabledFunctions(String functions, boolean forceRestart,
- boolean usbDataUnlocked) {
- if (DEBUG) {
- Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
- + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
- }
-
- if (usbDataUnlocked != mUsbDataUnlocked) {
- mUsbDataUnlocked = usbDataUnlocked;
- updateUsbNotification(false);
- forceRestart = true;
- }
-
- // Try to set the enabled functions.
- final String oldFunctions = mCurrentFunctions;
- final boolean oldFunctionsApplied = mCurrentFunctionsApplied;
- if (trySetEnabledFunctions(functions, forceRestart)) {
- return;
- }
-
- // Didn't work. Try to revert changes.
- // We always reapply the policy in case certain constraints changed such as
- // user restrictions independently of any other new functions we were
- // trying to activate.
- if (oldFunctionsApplied && !oldFunctions.equals(functions)) {
- Slog.e(TAG, "Failsafe 1: Restoring previous USB functions.");
- if (trySetEnabledFunctions(oldFunctions, false)) {
- return;
- }
- }
-
- // Still didn't work. Try to restore the default functions.
- Slog.e(TAG, "Failsafe 2: Restoring default USB functions.");
- if (trySetEnabledFunctions(null, false)) {
- return;
- }
-
- // Now we're desperate. Ignore the default functions.
- // Try to get ADB working if enabled.
- Slog.e(TAG, "Failsafe 3: Restoring empty function list (with ADB if enabled).");
- if (trySetEnabledFunctions(UsbManager.USB_FUNCTION_NONE, false)) {
- return;
- }
-
- // Ouch.
- Slog.e(TAG, "Unable to set any USB functions!");
- }
-
- private boolean isNormalBoot() {
- String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
- return bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown");
- }
-
- private boolean trySetEnabledFunctions(String functions, boolean forceRestart) {
- if (functions == null || applyAdbFunction(functions)
- .equals(UsbManager.USB_FUNCTION_NONE)) {
- functions = getChargingFunctions();
- }
- functions = applyAdbFunction(functions);
-
- String oemFunctions = applyOemOverrideFunction(functions);
-
- if (!isNormalBoot() && !mCurrentFunctions.equals(functions)) {
- SystemProperties.set(getPersistProp(true), functions);
- }
-
- if ((!functions.equals(oemFunctions) &&
- !mCurrentOemFunctions.equals(oemFunctions))
- || !mCurrentFunctions.equals(functions)
- || !mCurrentFunctionsApplied
- || forceRestart) {
- Slog.i(TAG, "Setting USB config to " + functions);
- mCurrentFunctions = functions;
- mCurrentOemFunctions = oemFunctions;
- mCurrentFunctionsApplied = false;
-
- // Kick the USB stack to close existing connections.
- setUsbConfig(UsbManager.USB_FUNCTION_NONE);
-
- if (!waitForState(UsbManager.USB_FUNCTION_NONE)) {
- Slog.e(TAG, "Failed to kick USB config");
- return false;
- }
-
- // Set the new USB configuration.
- setUsbConfig(oemFunctions);
-
- if (mBootCompleted
- && (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
- || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) {
- // Start up dependent services.
- updateUsbStateBroadcastIfNeeded(true);
- }
-
- if (!waitForState(oemFunctions)) {
- Slog.e(TAG, "Failed to switch USB config to " + functions);
- return false;
- }
-
- mCurrentFunctionsApplied = true;
- }
- return true;
- }
-
- private String applyAdbFunction(String functions) {
+ protected String applyAdbFunction(String functions) {
// Do not pass null pointer to the UsbManager.
// There isnt a check there.
if (functions == null) {
@@ -838,7 +706,7 @@
return false;
}
- private void updateUsbStateBroadcastIfNeeded(boolean configChanged) {
+ protected void updateUsbStateBroadcastIfNeeded(boolean configChanged) {
// send a sticky broadcast containing current USB state
Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
@@ -956,7 +824,8 @@
updateCurrentAccessory();
}
if (mBootCompleted) {
- if (!mConnected && !hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT)) {
+ if (!mConnected && !hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT)
+ && !hasMessages(MSG_FUNCTION_SWITCH_TIMEOUT)) {
// restore defaults when USB is disconnected
if (!mScreenLocked
&& !UsbManager.USB_FUNCTION_NONE.equals(
@@ -1082,7 +951,7 @@
if (!UsbManager.USB_FUNCTION_NONE.equals(mScreenUnlockedFunctions)
&& (UsbManager.USB_FUNCTION_ADB.equals(mCurrentFunctions)
|| (UsbManager.USB_FUNCTION_MTP.equals(mCurrentFunctions)
- && !mUsbDataUnlocked))) {
+ && !mUsbDataUnlocked))) {
// Set the screen unlocked functions if current function is charging.
setScreenUnlockedFunctions();
}
@@ -1097,9 +966,8 @@
mCurrentFunctions, forceRestart, mUsbDataUnlocked && !forceRestart);
break;
case MSG_SYSTEM_READY:
- updateUsbNotification(false);
- updateAdbNotification(false);
- updateUsbFunctions();
+ mSystemReady = true;
+ finishBoot();
break;
case MSG_LOCALE_CHANGED:
updateAdbNotification(true);
@@ -1107,23 +975,7 @@
break;
case MSG_BOOT_COMPLETED:
mBootCompleted = true;
- if (mPendingBootBroadcast) {
- updateUsbStateBroadcastIfNeeded(false);
- mPendingBootBroadcast = false;
- }
-
- if (!mScreenLocked
- && !UsbManager.USB_FUNCTION_NONE.equals(mScreenUnlockedFunctions)) {
- setScreenUnlockedFunctions();
- } else {
- setEnabledFunctions(null, false, false);
- }
- if (mCurrentAccessory != null) {
- getCurrentSettings().accessoryAttached(mCurrentAccessory);
- }
- if (mDebuggingManager != null) {
- mDebuggingManager.setAdbEnabled(mAdbEnabled);
- }
+ finishBoot();
break;
case MSG_USER_SWITCHED: {
if (mCurrentUser != msg.arg1) {
@@ -1153,6 +1005,41 @@
}
}
+ protected void finishBoot() {
+ if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
+ if (mPendingBootBroadcast) {
+ updateUsbStateBroadcastIfNeeded(false);
+ mPendingBootBroadcast = false;
+ }
+ if (!mScreenLocked
+ && !UsbManager.USB_FUNCTION_NONE.equals(mScreenUnlockedFunctions)) {
+ setScreenUnlockedFunctions();
+ } else {
+ setEnabledFunctions(null, false, false);
+ }
+ if (mCurrentAccessory != null) {
+ getCurrentSettings().accessoryAttached(mCurrentAccessory);
+ }
+ if (mDebuggingManager != null) {
+ mDebuggingManager.setAdbEnabled(mAdbEnabled);
+ }
+
+ // make sure the ADB_ENABLED setting value matches the current state
+ try {
+ Settings.Global.putInt(mContentResolver,
+ Settings.Global.ADB_ENABLED, mAdbEnabled ? 1 : 0);
+ } catch (SecurityException e) {
+ // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't
+ // be changed.
+ Slog.d(TAG, "ADB_ENABLED is restricted.");
+ }
+
+ updateUsbNotification(false);
+ updateAdbNotification(false);
+ updateUsbFunctions();
+ }
+ }
+
private boolean isUsbDataTransferActive() {
return UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)
|| UsbManager.containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP);
@@ -1162,7 +1049,7 @@
return mCurrentAccessory;
}
- private void updateUsbNotification(boolean force) {
+ protected void updateUsbNotification(boolean force) {
if (mNotificationManager == null || !mUseUsbNotification
|| ("0".equals(SystemProperties.get("persist.charging.notify")))) {
return;
@@ -1262,18 +1149,18 @@
}
Notification.Builder builder = new Notification.Builder(mContext, channel)
- .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
- .setWhen(0)
- .setOngoing(true)
- .setTicker(title)
- .setDefaults(0) // please be quiet
- .setColor(mContext.getColor(
- com.android.internal.R.color
- .system_notification_accent_color))
- .setContentTitle(title)
- .setContentText(message)
- .setContentIntent(pi)
- .setVisibility(Notification.VISIBILITY_PUBLIC);
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setWhen(0)
+ .setOngoing(true)
+ .setTicker(title)
+ .setDefaults(0) // please be quiet
+ .setColor(mContext.getColor(
+ com.android.internal.R.color
+ .system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(message)
+ .setContentIntent(pi)
+ .setVisibility(Notification.VISIBILITY_PUBLIC);
if (titleRes
== com.android.internal.R.string
@@ -1291,7 +1178,7 @@
}
}
- private void updateAdbNotification(boolean force) {
+ protected void updateAdbNotification(boolean force) {
if (mNotificationManager == null) return;
final int id = SystemMessage.NOTE_ADB_ACTIVE;
final int titleRes = com.android.internal.R.string.adb_active_notification_title;
@@ -1343,22 +1230,23 @@
}
}
- private String getChargingFunctions() {
- String func = SystemProperties.get(getPersistProp(true),
- UsbManager.USB_FUNCTION_NONE);
+ protected String getChargingFunctions() {
// if ADB is enabled, reset functions to ADB
// else enable MTP as usual.
- if (UsbManager.containsFunction(func, UsbManager.USB_FUNCTION_ADB)) {
+ if (mAdbEnabled) {
return UsbManager.USB_FUNCTION_ADB;
} else {
return UsbManager.USB_FUNCTION_MTP;
}
}
+ public boolean isFunctionEnabled(String function) {
+ return UsbManager.containsFunction(mCurrentFunctions, function);
+ }
+
public void dump(IndentingPrintWriter pw) {
pw.println("USB Device State:");
pw.println(" mCurrentFunctions: " + mCurrentFunctions);
- pw.println(" mCurrentOemFunctions: " + mCurrentOemFunctions);
pw.println(" mCurrentFunctionsApplied: " + mCurrentFunctionsApplied);
pw.println(" mScreenUnlockedFunctions: " + mScreenUnlockedFunctions);
pw.println(" mScreenLocked: " + mScreenLocked);
@@ -1372,6 +1260,7 @@
pw.println(" mUsbCharging: " + mUsbCharging);
pw.println(" mHideUsbNotification: " + mHideUsbNotification);
pw.println(" mAudioAccessoryConnected: " + mAudioAccessoryConnected);
+ pw.println(" mAdbEnabled: " + mAdbEnabled);
try {
pw.println(" Kernel state: "
@@ -1382,6 +1271,675 @@
pw.println("IOException: " + e);
}
}
+
+ /**
+ * Evaluates USB function policies and applies the change accordingly.
+ */
+ protected abstract void setEnabledFunctions(String functions, boolean forceRestart,
+ boolean usbDataUnlocked);
+
+ }
+
+ private final class UsbHandlerLegacy extends UsbHandler {
+ /**
+ * The non-persistent property which stores the current USB settings.
+ */
+ private static final String USB_CONFIG_PROPERTY = "sys.usb.config";
+
+ /**
+ * The non-persistent property which stores the current USB actual state.
+ */
+ private static final String USB_STATE_PROPERTY = "sys.usb.state";
+
+ private HashMap<String, HashMap<String, Pair<String, String>>> mOemModeMap;
+ private String mCurrentOemFunctions;
+
+ UsbHandlerLegacy(Looper looper, Context context) {
+ super(looper);
+ try {
+ readOemUsbOverrideConfig(context);
+ // Restore default functions.
+ mCurrentOemFunctions = SystemProperties.get(getPersistProp(false),
+ UsbManager.USB_FUNCTION_NONE);
+ if (isNormalBoot()) {
+ mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY,
+ UsbManager.USB_FUNCTION_NONE);
+ mCurrentFunctionsApplied = mCurrentFunctions.equals(
+ SystemProperties.get(USB_STATE_PROPERTY));
+ } else {
+ mCurrentFunctions = SystemProperties.get(getPersistProp(true),
+ UsbManager.USB_FUNCTION_NONE);
+ mCurrentFunctionsApplied = SystemProperties.get(USB_CONFIG_PROPERTY,
+ UsbManager.USB_FUNCTION_NONE).equals(
+ SystemProperties.get(USB_STATE_PROPERTY));
+ }
+ mCurrentUsbFunctionsReceived = true;
+
+ String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
+ updateState(state);
+
+ // register observer to listen for settings changes
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
+ false, new AdbSettingsObserver());
+
+ // Watch for USB configuration changes
+ mUEventObserver.startObserving(USB_STATE_MATCH);
+ mUEventObserver.startObserving(ACCESSORY_START_MATCH);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error initializing UsbHandler", e);
+ }
+ }
+
+ private void readOemUsbOverrideConfig(Context context) {
+ String[] configList = mContext.getResources().getStringArray(
+ com.android.internal.R.array.config_oemUsbModeOverride);
+
+ if (configList != null) {
+ for (String config : configList) {
+ String[] items = config.split(":");
+ if (items.length == 3 || items.length == 4) {
+ if (mOemModeMap == null) {
+ mOemModeMap = new HashMap<>();
+ }
+ HashMap<String, Pair<String, String>> overrideMap =
+ mOemModeMap.get(items[0]);
+ if (overrideMap == null) {
+ overrideMap = new HashMap<>();
+ mOemModeMap.put(items[0], overrideMap);
+ }
+
+ // Favoring the first combination if duplicate exists
+ if (!overrideMap.containsKey(items[1])) {
+ if (items.length == 3) {
+ overrideMap.put(items[1], new Pair<>(items[2], ""));
+ } else {
+ overrideMap.put(items[1], new Pair<>(items[2], items[3]));
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private String applyOemOverrideFunction(String usbFunctions) {
+ if ((usbFunctions == null) || (mOemModeMap == null)) {
+ return usbFunctions;
+ }
+
+ String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+ Slog.d(TAG, "applyOemOverride usbfunctions=" + usbFunctions + " bootmode=" + bootMode);
+
+ Map<String, Pair<String, String>> overridesMap =
+ mOemModeMap.get(bootMode);
+ // Check to ensure that the oem is not overriding in the normal
+ // boot mode
+ if (overridesMap != null && !(bootMode.equals(NORMAL_BOOT)
+ || bootMode.equals("unknown"))) {
+ Pair<String, String> overrideFunctions =
+ overridesMap.get(usbFunctions);
+ if (overrideFunctions != null) {
+ Slog.d(TAG, "OEM USB override: " + usbFunctions
+ + " ==> " + overrideFunctions.first
+ + " persist across reboot "
+ + overrideFunctions.second);
+ if (!overrideFunctions.second.equals("")) {
+ String newFunction;
+ if (mAdbEnabled) {
+ newFunction = UsbManager.addFunction(overrideFunctions.second,
+ UsbManager.USB_FUNCTION_ADB);
+ } else {
+ newFunction = overrideFunctions.second;
+ }
+ Slog.d(TAG, "OEM USB override persisting: " + newFunction + "in prop: "
+ + getPersistProp(false));
+ SystemProperties.set(getPersistProp(false),
+ newFunction);
+ }
+ return overrideFunctions.first;
+ } else if (mAdbEnabled) {
+ String newFunction = UsbManager.addFunction(UsbManager.USB_FUNCTION_NONE,
+ UsbManager.USB_FUNCTION_ADB);
+ SystemProperties.set(getPersistProp(false),
+ newFunction);
+ } else {
+ SystemProperties.set(getPersistProp(false),
+ UsbManager.USB_FUNCTION_NONE);
+ }
+ }
+ // return passed in functions as is.
+ return usbFunctions;
+ }
+
+ private boolean waitForState(String state) {
+ // wait for the transition to complete.
+ // give up after 1 second.
+ String value = null;
+ for (int i = 0; i < 20; i++) {
+ // State transition is done when sys.usb.state is set to the new configuration
+ value = SystemProperties.get(USB_STATE_PROPERTY);
+ if (state.equals(value)) return true;
+ SystemClock.sleep(50);
+ }
+ Slog.e(TAG, "waitForState(" + state + ") FAILED: got " + value);
+ return false;
+ }
+
+ private void setUsbConfig(String config) {
+ if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")");
+ /**
+ * set the new configuration
+ * we always set it due to b/23631400, where adbd was getting killed
+ * and not restarted due to property timeouts on some devices
+ */
+ SystemProperties.set(USB_CONFIG_PROPERTY, config);
+ }
+
+ @Override
+ protected void setEnabledFunctions(String functions, boolean forceRestart,
+ boolean usbDataUnlocked) {
+ if (DEBUG) {
+ Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
+ + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
+ }
+
+ if (usbDataUnlocked != mUsbDataUnlocked) {
+ mUsbDataUnlocked = usbDataUnlocked;
+ updateUsbNotification(false);
+ forceRestart = true;
+ }
+
+ /**
+ * Try to set the enabled functions.
+ */
+ final String oldFunctions = mCurrentFunctions;
+ final boolean oldFunctionsApplied = mCurrentFunctionsApplied;
+ if (trySetEnabledFunctions(functions, forceRestart)) {
+ return;
+ }
+
+ /**
+ * Didn't work. Try to revert changes.
+ * We always reapply the policy in case certain constraints changed such as
+ * user restrictions independently of any other new functions we were
+ * trying to activate.
+ */
+ if (oldFunctionsApplied && !oldFunctions.equals(functions)) {
+ Slog.e(TAG, "Failsafe 1: Restoring previous USB functions.");
+ if (trySetEnabledFunctions(oldFunctions, false)) {
+ return;
+ }
+ }
+
+ /**
+ * Still didn't work. Try to restore the default functions.
+ */
+ Slog.e(TAG, "Failsafe 2: Restoring default USB functions.");
+ if (trySetEnabledFunctions(null, false)) {
+ return;
+ }
+
+ /**
+ * Now we're desperate. Ignore the default functions.
+ * Try to get ADB working if enabled.
+ */
+ Slog.e(TAG, "Failsafe 3: Restoring empty function list (with ADB if enabled).");
+ if (trySetEnabledFunctions(UsbManager.USB_FUNCTION_NONE, false)) {
+ return;
+ }
+
+ /**
+ * Ouch.
+ */
+ Slog.e(TAG, "Unable to set any USB functions!");
+ }
+
+ private boolean isNormalBoot() {
+ String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+ return bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown");
+ }
+
+ private boolean trySetEnabledFunctions(String functions, boolean forceRestart) {
+ if (functions == null || applyAdbFunction(functions)
+ .equals(UsbManager.USB_FUNCTION_NONE)) {
+ functions = getChargingFunctions();
+ }
+ functions = applyAdbFunction(functions);
+
+ String oemFunctions = applyOemOverrideFunction(functions);
+
+ if (!isNormalBoot() && !mCurrentFunctions.equals(functions)) {
+ SystemProperties.set(getPersistProp(true), functions);
+ }
+
+ if ((!functions.equals(oemFunctions)
+ && !mCurrentOemFunctions.equals(oemFunctions))
+ || !mCurrentFunctions.equals(functions)
+ || !mCurrentFunctionsApplied
+ || forceRestart) {
+ Slog.i(TAG, "Setting USB config to " + functions);
+ mCurrentFunctions = functions;
+ mCurrentOemFunctions = oemFunctions;
+ mCurrentFunctionsApplied = false;
+
+ /**
+ * Kick the USB stack to close existing connections.
+ */
+ setUsbConfig(UsbManager.USB_FUNCTION_NONE);
+
+ if (!waitForState(UsbManager.USB_FUNCTION_NONE)) {
+ Slog.e(TAG, "Failed to kick USB config");
+ return false;
+ }
+
+ /**
+ * Set the new USB configuration.
+ */
+ setUsbConfig(oemFunctions);
+
+ if (mBootCompleted
+ && (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
+ || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) {
+ /**
+ * Start up dependent services.
+ */
+ updateUsbStateBroadcastIfNeeded(true);
+ }
+
+ if (!waitForState(oemFunctions)) {
+ Slog.e(TAG, "Failed to switch USB config to " + functions);
+ return false;
+ }
+
+ mCurrentFunctionsApplied = true;
+ }
+ return true;
+ }
+
+ private String getPersistProp(boolean functions) {
+ String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+ String persistProp = USB_PERSISTENT_CONFIG_PROPERTY;
+ if (!(bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown"))) {
+ if (functions) {
+ persistProp = "persist.sys.usb." + bootMode + ".func";
+ } else {
+ persistProp = "persist.sys.usb." + bootMode + ".config";
+ }
+ }
+ return persistProp;
+ }
+ }
+
+ private final class UsbHandlerHal extends UsbHandler {
+
+ /**
+ * Proxy object for the usb gadget hal daemon.
+ */
+ @GuardedBy("mGadgetProxyLock")
+ private IUsbGadget mGadgetProxy;
+
+ private final Object mGadgetProxyLock = new Object();
+
+ /**
+ * Cookie sent for usb gadget hal death notification.
+ */
+ private static final int USB_GADGET_HAL_DEATH_COOKIE = 2000;
+
+ /**
+ * Keeps track of the latest setCurrentUsbFunctions request number.
+ */
+ private int mCurrentRequest = 0;
+
+ /**
+ * The maximum time for which the UsbDeviceManager would wait once
+ * setCurrentUsbFunctions is called.
+ */
+ private static final int SET_FUNCTIONS_TIMEOUT_MS = 3000;
+
+ /**
+ * Conseration leeway to make sure that the hal callback arrives before
+ * SET_FUNCTIONS_TIMEOUT_MS expires. If the callback does not arrive
+ * within SET_FUNCTIONS_TIMEOUT_MS, UsbDeviceManager retries enabling
+ * default functions.
+ */
+ private static final int SET_FUNCTIONS_LEEWAY_MS = 500;
+
+ /**
+ * While switching functions, a disconnect is excpect as the usb gadget
+ * us torn down and brought back up. Wait for SET_FUNCTIONS_TIMEOUT_MS +
+ * ENUMERATION_TIME_OUT_MS before switching back to default fumctions when
+ * switching functions.
+ */
+ private static final int ENUMERATION_TIME_OUT_MS = 2000;
+
+ /**
+ * Command to start native service.
+ */
+ protected static final String CTL_START = "ctl.start";
+
+ /**
+ * Command to start native service.
+ */
+ protected static final String CTL_STOP = "ctl.stop";
+
+ /**
+ * Adb natvie daemon
+ */
+ protected static final String ADBD = "adbd";
+
+
+ UsbHandlerHal(Looper looper) {
+ super(looper);
+ try {
+ ServiceNotification serviceNotification = new ServiceNotification();
+
+ boolean ret = IServiceManager.getService()
+ .registerForNotifications("android.hardware.usb.gadget@1.0::IUsbGadget",
+ "", serviceNotification);
+ if (!ret) {
+ Slog.e(TAG, "Failed to register usb gadget service start notification");
+ return;
+ }
+
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy = IUsbGadget.getService(true);
+ mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
+ USB_GADGET_HAL_DEATH_COOKIE);
+ mCurrentFunctions = UsbManager.USB_FUNCTION_NONE;
+ mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
+ mCurrentUsbFunctionsRequested = true;
+ }
+ String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
+ updateState(state);
+
+ /**
+ * Register observer to listen for settings changes.
+ */
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
+ false, new AdbSettingsObserver());
+
+ /**
+ * Watch for USB configuration changes.
+ */
+ mUEventObserver.startObserving(USB_STATE_MATCH);
+ mUEventObserver.startObserving(ACCESSORY_START_MATCH);
+ } catch (NoSuchElementException e) {
+ Slog.e(TAG, "Usb gadget hal not found", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Usb Gadget hal not responding", e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error initializing UsbHandler", e);
+ }
+ }
+
+
+ final class UsbGadgetDeathRecipient implements HwBinder.DeathRecipient {
+ @Override
+ public void serviceDied(long cookie) {
+ if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
+ Slog.e(TAG, "Usb Gadget hal service died cookie: " + cookie);
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy = null;
+ }
+ }
+ }
+ }
+
+ final class ServiceNotification extends IServiceNotification.Stub {
+ @Override
+ public void onRegistration(String fqName, String name, boolean preexisting) {
+ Slog.i(TAG, "Usb gadget hal service started " + fqName + " " + name);
+ synchronized (mGadgetProxyLock) {
+ try {
+ mGadgetProxy = IUsbGadget.getService();
+ mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
+ USB_GADGET_HAL_DEATH_COOKIE);
+ if (!mCurrentFunctionsApplied) {
+ setCurrentFunctions(mCurrentFunctions, mUsbDataUnlocked);
+ }
+ } catch (NoSuchElementException e) {
+ Slog.e(TAG, "Usb gadget hal not found", e);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Usb Gadget hal not responding", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SET_CHARGING_FUNCTIONS:
+ setEnabledFunctions(null, false, mUsbDataUnlocked);
+ break;
+ case MSG_SET_FUNCTIONS_TIMEOUT:
+ Slog.e(TAG, "Set functions timed out! no reply from usb hal");
+ if (msg.arg1 != 1) {
+ setEnabledFunctions(null, false, mUsbDataUnlocked);
+ }
+ break;
+ case MSG_GET_CURRENT_USB_FUNCTIONS:
+ Slog.e(TAG, "prcessing MSG_GET_CURRENT_USB_FUNCTIONS");
+ mCurrentUsbFunctionsReceived = true;
+
+ if (mCurrentUsbFunctionsRequested) {
+ Slog.e(TAG, "updating mCurrentFunctions");
+ mCurrentFunctions = functionListToString((Long) msg.obj);
+ Slog.e(TAG,
+ "mCurrentFunctions:" + mCurrentFunctions + "applied:" + msg.arg1);
+ mCurrentFunctionsApplied = msg.arg1 == 1;
+ }
+ finishBoot();
+ break;
+ case MSG_FUNCTION_SWITCH_TIMEOUT:
+ /**
+ * Dont force to default when the configuration is already set to default.
+ */
+ if (msg.arg1 != 1) {
+ setEnabledFunctions(null, !mAdbEnabled, false);
+ }
+ break;
+ default:
+ super.handleMessage(msg);
+ }
+ }
+
+ private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+ int mRequest;
+ long mFunctions;
+ boolean mChargingFunctions;
+
+ UsbGadgetCallback() {
+ }
+
+ UsbGadgetCallback(int request, long functions,
+ boolean chargingFunctions) {
+ mRequest = request;
+ mFunctions = functions;
+ mChargingFunctions = chargingFunctions;
+ }
+
+ @Override
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status) {
+ /**
+ * Callback called for a previous setCurrenUsbFunction
+ */
+ if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
+ || (mFunctions != functions)) {
+ return;
+ }
+
+ removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
+ Slog.e(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
+ if (status == Status.SUCCESS) {
+ mCurrentFunctionsApplied = true;
+ } else if (!mChargingFunctions) {
+ Slog.e(TAG, "Setting default fuctions");
+ sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
+ }
+ }
+
+ @Override
+ public void getCurrentUsbFunctionsCb(long functions,
+ int status) {
+ sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
+ status == Status.FUNCTIONS_APPLIED);
+ }
+ }
+
+ private long stringToFunctionList(String config) {
+ long functionsMask = 0;
+ String[] functions = config.split(",");
+ for (int i = 0; i < functions.length; i++) {
+ switch (functions[i]) {
+ case "none":
+ functionsMask |= GadgetFunction.NONE;
+ break;
+ case "adb":
+ functionsMask |= GadgetFunction.ADB;
+ break;
+ case "mtp":
+ functionsMask |= GadgetFunction.MTP;
+ break;
+ case "ptp":
+ functionsMask |= GadgetFunction.PTP;
+ break;
+ case "midi":
+ functionsMask |= GadgetFunction.MIDI;
+ break;
+ case "accessory":
+ functionsMask |= GadgetFunction.ACCESSORY;
+ break;
+ case "rndis":
+ functionsMask |= GadgetFunction.RNDIS;
+ break;
+ }
+ }
+ return functionsMask;
+ }
+
+ private String functionListToString(Long functionList) {
+ StringJoiner functions = new StringJoiner(",");
+ if (functionList == GadgetFunction.NONE) {
+ functions.add("none");
+ return functions.toString();
+ }
+ if ((functionList & GadgetFunction.ADB) != 0) {
+ functions.add("adb");
+ }
+ if ((functionList & GadgetFunction.MTP) != 0) {
+ functions.add("mtp");
+ }
+ if ((functionList & GadgetFunction.PTP) != 0) {
+ functions.add("ptp");
+ }
+ if ((functionList & GadgetFunction.MIDI) != 0) {
+ functions.add("midi");
+ }
+ if ((functionList & GadgetFunction.ACCESSORY) != 0) {
+ functions.add("accessory");
+ }
+ if ((functionList & GadgetFunction.RNDIS) != 0) {
+ functions.add("rndis");
+ }
+ /**
+ * Remove the trailing comma.
+ */
+ return functions.toString();
+ }
+
+
+ private void setUsbConfig(String config, boolean chargingFunctions) {
+ Long functions = stringToFunctionList(config);
+ if (true) Slog.d(TAG, "setUsbConfig(" + config + ") request:" + ++mCurrentRequest);
+ /**
+ * Cancel any ongoing requests, if present.
+ */
+ removeMessages(MSG_FUNCTION_SWITCH_TIMEOUT);
+ removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
+ removeMessages(MSG_SET_CHARGING_FUNCTIONS);
+
+ synchronized (mGadgetProxyLock) {
+ if (mGadgetProxy == null) {
+ Slog.e(TAG, "setUsbConfig mGadgetProxy is null");
+ return;
+ }
+ try {
+ if ((functions & GadgetFunction.ADB) != 0) {
+ /**
+ * Start adbd if ADB function is included in the configuration.
+ */
+ SystemProperties.set(CTL_START, ADBD);
+ } else {
+ /**
+ * Stop adbd otherwise.
+ */
+ SystemProperties.set(CTL_STOP, ADBD);
+ }
+ UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest,
+ functions, chargingFunctions);
+ mGadgetProxy.setCurrentUsbFunctions(functions, usbGadgetCallback,
+ SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS);
+ sendMessageDelayed(MSG_SET_FUNCTIONS_TIMEOUT, chargingFunctions,
+ SET_FUNCTIONS_TIMEOUT_MS);
+ sendMessageDelayed(MSG_FUNCTION_SWITCH_TIMEOUT, chargingFunctions,
+ SET_FUNCTIONS_TIMEOUT_MS + ENUMERATION_TIME_OUT_MS);
+ if (DEBUG) Slog.d(TAG, "timeout message queued");
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remoteexception while calling setCurrentUsbFunctions", e);
+ }
+ }
+ }
+
+ @Override
+ protected void setEnabledFunctions(String functions, boolean forceRestart,
+ boolean usbDataUnlocked) {
+ if (DEBUG) {
+ Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
+ + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
+ }
+
+ if (usbDataUnlocked != mUsbDataUnlocked) {
+ mUsbDataUnlocked = usbDataUnlocked;
+ updateUsbNotification(false);
+ forceRestart = true;
+ }
+
+ trySetEnabledFunctions(functions, forceRestart);
+ }
+
+ private void trySetEnabledFunctions(String functions, boolean forceRestart) {
+ boolean chargingFunctions = false;
+
+ if (functions == null || applyAdbFunction(functions)
+ .equals(UsbManager.USB_FUNCTION_NONE)) {
+ functions = getChargingFunctions();
+ chargingFunctions = true;
+ }
+ functions = applyAdbFunction(functions);
+
+ if (!mCurrentFunctions.equals(functions)
+ || !mCurrentFunctionsApplied
+ || forceRestart) {
+ Slog.i(TAG, "Setting USB config to " + functions);
+ mCurrentFunctions = functions;
+ mCurrentFunctionsApplied = false;
+ // set the flag to false as that would be stale value
+ mCurrentUsbFunctionsRequested = false;
+
+ // Set the new USB configuration.
+ setUsbConfig(mCurrentFunctions, chargingFunctions);
+
+ if (mBootCompleted
+ && (UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_MTP)
+ || UsbManager.containsFunction(functions, UsbManager.USB_FUNCTION_PTP))) {
+ // Start up dependent services.
+ updateUsbStateBroadcastIfNeeded(true);
+ }
+ }
+ }
}
/* returns the currently attached USB accessory */
@@ -1389,7 +1947,11 @@
return mHandler.getCurrentAccessory();
}
- /* opens the currently attached USB accessory */
+ /**
+ * opens the currently attached USB accessory.
+ *
+ * @param accessory accessory to be openened.
+ */
public ParcelFileDescriptor openAccessory(UsbAccessory accessory,
UsbUserSettingsManager settings) {
UsbAccessory currentAccessory = mHandler.getCurrentAccessory();
@@ -1406,20 +1968,32 @@
return nativeOpenAccessory();
}
+ /**
+ * Checks whether the function is present in the USB configuration.
+ *
+ * @param function function to be checked.
+ */
public boolean isFunctionEnabled(String function) {
- return UsbManager.containsFunction(SystemProperties.get(USB_CONFIG_PROPERTY), function);
+ return mHandler.isFunctionEnabled(function);
}
+ /**
+ * Adds function to the current USB configuration.
+ *
+ * @param functions name of the USB function, or null to restore the default function.
+ * @param usbDataUnlocked whether user data is accessible.
+ */
public void setCurrentFunctions(String functions, boolean usbDataUnlocked) {
if (DEBUG) {
- Slog.d(TAG, "setCurrentFunctions(" + functions + ", " +
- usbDataUnlocked + ")");
+ Slog.d(TAG, "setCurrentFunctions(" + functions + ", "
+ + usbDataUnlocked + ")");
}
mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, usbDataUnlocked);
}
/**
* Sets the functions which are set when the screen is unlocked.
+ *
* @param functions Functions to set.
*/
public void setScreenUnlockedFunctions(String functions) {
@@ -1429,101 +2003,6 @@
mHandler.sendMessage(MSG_SET_SCREEN_UNLOCKED_FUNCTIONS, functions);
}
- private void readOemUsbOverrideConfig() {
- String[] configList = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_oemUsbModeOverride);
-
- if (configList != null) {
- for (String config : configList) {
- String[] items = config.split(":");
- if (items.length == 3 || items.length == 4) {
- if (mOemModeMap == null) {
- mOemModeMap = new HashMap<>();
- }
- HashMap<String, Pair<String, String>> overrideMap
- = mOemModeMap.get(items[0]);
- if (overrideMap == null) {
- overrideMap = new HashMap<>();
- mOemModeMap.put(items[0], overrideMap);
- }
-
- // Favoring the first combination if duplicate exists
- if (!overrideMap.containsKey(items[1])) {
- if (items.length == 3) {
- overrideMap.put(items[1], new Pair<>(items[2], ""));
- } else {
- overrideMap.put(items[1], new Pair<>(items[2], items[3]));
- }
- }
- }
- }
- }
- }
-
- private String applyOemOverrideFunction(String usbFunctions) {
- if ((usbFunctions == null) || (mOemModeMap == null)) {
- return usbFunctions;
- }
-
- String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
- Slog.d(TAG, "applyOemOverride usbfunctions=" + usbFunctions + " bootmode=" + bootMode);
-
- Map<String, Pair<String, String>> overridesMap =
- mOemModeMap.get(bootMode);
- // Check to ensure that the oem is not overriding in the normal
- // boot mode
- if (overridesMap != null && !(bootMode.equals(NORMAL_BOOT) ||
- bootMode.equals("unknown"))) {
- Pair<String, String> overrideFunctions =
- overridesMap.get(usbFunctions);
- if (overrideFunctions != null) {
- Slog.d(TAG, "OEM USB override: " + usbFunctions
- + " ==> " + overrideFunctions.first
- + " persist across reboot "
- + overrideFunctions.second);
- if (!overrideFunctions.second.equals("")) {
- String newFunction;
- if (mAdbEnabled) {
- newFunction = UsbManager.addFunction(overrideFunctions.second,
- UsbManager.USB_FUNCTION_ADB);
- } else {
- newFunction = overrideFunctions.second;
- }
- Slog.d(TAG, "OEM USB override persisting: " + newFunction + "in prop: "
- + UsbDeviceManager.getPersistProp(false));
- SystemProperties.set(UsbDeviceManager.getPersistProp(false),
- newFunction);
- }
- return overrideFunctions.first;
- } else if (mAdbEnabled) {
- String newFunction = UsbManager.addFunction(UsbManager.USB_FUNCTION_NONE,
- UsbManager.USB_FUNCTION_ADB);
- SystemProperties.set(UsbDeviceManager.getPersistProp(false),
- newFunction);
- } else {
- SystemProperties.set(UsbDeviceManager.getPersistProp(false),
- UsbManager.USB_FUNCTION_NONE);
- }
- }
- // return passed in functions as is.
- return usbFunctions;
- }
-
- public static String getPersistProp(boolean functions) {
- String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
- String persistProp = USB_PERSISTENT_CONFIG_PROPERTY;
- if (!(bootMode.equals(NORMAL_BOOT) || bootMode.equals("unknown"))) {
- if (functions) {
- persistProp = "persist.sys.usb." + bootMode + ".func";
- } else {
- persistProp = "persist.sys.usb." + bootMode + ".config";
- }
- }
-
- return persistProp;
- }
-
-
public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
if (mDebuggingManager != null) {
mDebuggingManager.allowUsbDebugging(alwaysAllow, publicKey);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index d17bdc8..6799417 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -1960,6 +1960,15 @@
}
}
+ /** {@hide} */
+ final void internalOnHandoverComplete() {
+ for (CallbackRecord<Callback> record : mCallbackRecords) {
+ final Call call = this;
+ final Callback callback = record.getCallback();
+ record.getHandler().post(() -> callback.onHandoverComplete(call));
+ }
+ }
+
private void fireStateChanged(final int newState) {
for (CallbackRecord<Callback> record : mCallbackRecords) {
final Call call = this;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 7522443..63f970a 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -2801,6 +2801,15 @@
public void onCallEvent(String event, Bundle extras) {}
/**
+ * Notifies this {@link Connection} that a handover has completed.
+ * <p>
+ * A handover is initiated with {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int,
+ * Bundle)} on the initiating side of the handover, and
+ * {@link TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)}.
+ */
+ public void onHandoverComplete() {}
+
+ /**
* Notifies this {@link Connection} of a change to the extras made outside the
* {@link ConnectionService}.
* <p>
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 6af01ae..c1040ad 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -140,6 +140,7 @@
private static final String SESSION_POST_DIAL_CONT = "CS.oPDC";
private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
+ private static final String SESSION_HANDOVER_COMPLETE = "CS.hC";
private static final String SESSION_EXTRAS_CHANGED = "CS.oEC";
private static final String SESSION_START_RTT = "CS.+RTT";
private static final String SESSION_STOP_RTT = "CS.-RTT";
@@ -179,6 +180,7 @@
private static final int MSG_CONNECTION_SERVICE_FOCUS_LOST = 30;
private static final int MSG_CONNECTION_SERVICE_FOCUS_GAINED = 31;
private static final int MSG_HANDOVER_FAILED = 32;
+ private static final int MSG_HANDOVER_COMPLETE = 33;
private static Connection sNullConnection;
@@ -298,6 +300,19 @@
}
@Override
+ public void handoverComplete(String callId, Session.Info sessionInfo) {
+ Log.startSession(sessionInfo, SESSION_HANDOVER_COMPLETE);
+ try {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = callId;
+ args.arg2 = Log.createSubsession();
+ mHandler.obtainMessage(MSG_HANDOVER_COMPLETE, args).sendToTarget();
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void abort(String callId, Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_ABORT);
try {
@@ -1028,6 +1043,19 @@
}
break;
}
+ case MSG_HANDOVER_COMPLETE: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ Log.continueSession((Session) args.arg2,
+ SESSION_HANDLER + SESSION_HANDOVER_COMPLETE);
+ String callId = (String) args.arg1;
+ notifyHandoverComplete(callId);
+ } finally {
+ args.recycle();
+ Log.endSession();
+ }
+ break;
+ }
case MSG_ON_EXTRAS_CHANGED: {
SomeArgs args = (SomeArgs) msg.obj;
try {
@@ -1445,19 +1473,24 @@
final ConnectionRequest request,
boolean isIncoming,
boolean isUnknown) {
+ boolean isLegacyHandover = request.getExtras() != null &&
+ request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER, false);
+ boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
+ TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
- "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
- isIncoming,
- isUnknown);
+ "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b",
+ callManagerAccount, callId, request, isIncoming, isUnknown, isLegacyHandover,
+ isHandover);
Connection connection = null;
- if (getApplicationContext().getApplicationInfo().targetSdkVersion >
- Build.VERSION_CODES.O_MR1 && request.getExtras() != null &&
- request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER,false)) {
+ if (isHandover) {
+ PhoneAccountHandle fromPhoneAccountHandle = request.getExtras() != null
+ ? (PhoneAccountHandle) request.getExtras().getParcelable(
+ TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT) : null;
if (!isIncoming) {
- connection = onCreateOutgoingHandoverConnection(callManagerAccount, request);
+ connection = onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request);
} else {
- connection = onCreateIncomingHandoverConnection(callManagerAccount, request);
+ connection = onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request);
}
} else {
connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
@@ -1754,6 +1787,19 @@
}
/**
+ * Notifies a {@link Connection} that a handover has completed.
+ *
+ * @param callId The ID of the call which completed handover.
+ */
+ private void notifyHandoverComplete(String callId) {
+ Log.d(this, "notifyHandoverComplete(%s)", callId);
+ Connection connection = findConnectionForAction(callId, "notifyHandoverComplete");
+ if (connection != null) {
+ connection.onHandoverComplete();
+ }
+ }
+
+ /**
* Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom.
* <p>
* These extra changes can originate from Telecom itself, or from an {@link InCallService} via
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 74fa62d..fcf04c9 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -81,6 +81,7 @@
private static final int MSG_ON_RTT_UPGRADE_REQUEST = 10;
private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
private static final int MSG_ON_HANDOVER_FAILED = 12;
+ private static final int MSG_ON_HANDOVER_COMPLETE = 13;
/** Default Handler used to consolidate binder method calls onto a single thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -157,6 +158,11 @@
mPhone.internalOnHandoverFailed(callId, error);
break;
}
+ case MSG_ON_HANDOVER_COMPLETE: {
+ String callId = (String) msg.obj;
+ mPhone.internalOnHandoverComplete(callId);
+ break;
+ }
default:
break;
}
@@ -237,6 +243,11 @@
public void onHandoverFailed(String callId, int error) {
mHandler.obtainMessage(MSG_ON_HANDOVER_FAILED, error, 0, callId).sendToTarget();
}
+
+ @Override
+ public void onHandoverComplete(String callId) {
+ mHandler.obtainMessage(MSG_ON_HANDOVER_COMPLETE, callId).sendToTarget();
+ }
}
private Phone.Listener mPhoneListener = new Phone.Listener() {
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index b5394b9..99f94f2 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -230,6 +230,13 @@
}
}
+ final void internalOnHandoverComplete(String callId) {
+ Call call = mCallByTelecomCallId.get(callId);
+ if (call != null) {
+ call.internalOnHandoverComplete();
+ }
+ }
+
/**
* Called to destroy the phone and cleanup any lingering calls.
*/
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 91d5da3..1fe5db5 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -111,6 +111,12 @@
"android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS";
/**
+ * The {@link android.content.Intent} action used to show the assisted dialing settings.
+ */
+ public static final String ACTION_SHOW_ASSISTED_DIALING_SETTINGS =
+ "android.telecom.action.SHOW_ASSISTED_DIALING_SETTINGS";
+
+ /**
* The {@link android.content.Intent} action used to show the settings page used to configure
* {@link PhoneAccount} preferences.
*/
@@ -379,6 +385,17 @@
public static final String EXTRA_IS_HANDOVER = "android.telecom.extra.IS_HANDOVER";
/**
+ * When {@code true} indicates that a request to create a new connection is for the purpose of
+ * a handover. Note: This is used with the
+ * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)} API as part of the
+ * internal communication mechanism with the {@link android.telecom.ConnectionService}. It is
+ * not the same as the legacy {@link #EXTRA_IS_HANDOVER} extra.
+ * @hide
+ */
+ public static final String EXTRA_IS_HANDOVER_CONNECTION =
+ "android.telecom.extra.IS_HANDOVER_CONNECTION";
+
+ /**
* Parcelable extra used with {@link #EXTRA_IS_HANDOVER} to indicate the source
* {@link PhoneAccountHandle} when initiating a handover which {@link ConnectionService}
* the handover is from.
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 3d04bfc..3a84f00 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -107,4 +107,6 @@
void handoverFailed(String callId, in ConnectionRequest request,
int error, in Session.Info sessionInfo);
+
+ void handoverComplete(String callId, in Session.Info sessionInfo);
}
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index 110109e..b9563fa 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -56,4 +56,6 @@
void onRttInitiationFailure(String callId, int reason);
void onHandoverFailed(String callId, int error);
+
+ void onHandoverComplete(String callId);
}
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 703f96d..7cd16128 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -24,6 +24,7 @@
public final class AccessNetworkConstants {
public static final class AccessNetworkType {
+ public static final int UNKNOWN = 0;
public static final int GERAN = 1;
public static final int UTRAN = 2;
public static final int EUTRAN = 3;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index cbc9428..91d86c6 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -39,13 +39,29 @@
private final static String TAG = "CarrierConfigManager";
/**
+ * Extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate the slot index that the
+ * broadcast is for.
+ */
+ public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
+
+ /**
+ * Optional extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate the
+ * subscription index that the broadcast is for, if a valid one is available.
+ */
+ public static final String EXTRA_SUBSCRIPTION_INDEX =
+ SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX;
+
+ /**
* @hide
*/
public CarrierConfigManager() {
}
/**
- * This intent is broadcast by the system when carrier config changes.
+ * This intent is broadcast by the system when carrier config changes. An int is specified in
+ * {@link #EXTRA_SLOT_INDEX} to indicate the slot index that this is for. An optional int extra
+ * {@link #EXTRA_SUBSCRIPTION_INDEX} is included to indicate the subscription index if a valid
+ * one is available for the slot index.
*/
public static final String
ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index 7f20c8a..5f1f448 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -40,6 +40,8 @@
private final String mAlphaLong;
// short alpha Operator Name String or Enhanced Operator Name String
private final String mAlphaShort;
+ // cell bandwidth, in kHz
+ private final int mBandwidth;
/**
* @hide
@@ -50,6 +52,7 @@
mPci = Integer.MAX_VALUE;
mTac = Integer.MAX_VALUE;
mEarfcn = Integer.MAX_VALUE;
+ mBandwidth = Integer.MAX_VALUE;
mAlphaLong = null;
mAlphaShort = null;
}
@@ -65,7 +68,8 @@
* @hide
*/
public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac) {
- this(ci, pci, tac, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), null, null);
+ this(ci, pci, tac, Integer.MAX_VALUE, Integer.MAX_VALUE, String.valueOf(mcc),
+ String.valueOf(mnc), null, null);
}
/**
@@ -80,7 +84,8 @@
* @hide
*/
public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac, int earfcn) {
- this(ci, pci, tac, earfcn, String.valueOf(mcc), String.valueOf(mnc), null, null);
+ this(ci, pci, tac, earfcn, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc),
+ null, null);
}
/**
@@ -89,6 +94,7 @@
* @param pci Physical Cell Id 0..503
* @param tac 16-bit Tracking Area Code
* @param earfcn 18-bit LTE Absolute RF Channel Number
+ * @param bandwidth cell bandwidth in kHz
* @param mccStr 3-digit Mobile Country Code in string format
* @param mncStr 2 or 3-digit Mobile Network Code in string format
* @param alphal long alpha Operator Name String or Enhanced Operator Name String
@@ -96,19 +102,20 @@
*
* @hide
*/
- public CellIdentityLte(int ci, int pci, int tac, int earfcn, String mccStr,
- String mncStr, String alphal, String alphas) {
+ public CellIdentityLte(int ci, int pci, int tac, int earfcn, int bandwidth, String mccStr,
+ String mncStr, String alphal, String alphas) {
super(TAG, TYPE_LTE, mccStr, mncStr);
mCi = ci;
mPci = pci;
mTac = tac;
mEarfcn = earfcn;
+ mBandwidth = bandwidth;
mAlphaLong = alphal;
mAlphaShort = alphas;
}
private CellIdentityLte(CellIdentityLte cid) {
- this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mMccStr,
+ this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mBandwidth, cid.mMccStr,
cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
}
@@ -163,6 +170,13 @@
}
/**
+ * @return Cell bandwidth in kHz, Integer.MAX_VALUE if unknown
+ */
+ public int getBandwidth() {
+ return mBandwidth;
+ }
+
+ /**
* @return Mobile Country Code in string format, null if unknown
*/
public String getMccStr() {
@@ -219,6 +233,7 @@
&& mPci == o.mPci
&& mTac == o.mTac
&& mEarfcn == o.mEarfcn
+ && mBandwidth == o.mBandwidth
&& TextUtils.equals(mMccStr, o.mMccStr)
&& TextUtils.equals(mMncStr, o.mMncStr)
&& TextUtils.equals(mAlphaLong, o.mAlphaLong)
@@ -232,6 +247,7 @@
.append(" mPci=").append(mPci)
.append(" mTac=").append(mTac)
.append(" mEarfcn=").append(mEarfcn)
+ .append(" mBandwidth=").append(mBandwidth)
.append(" mMcc=").append(mMccStr)
.append(" mMnc=").append(mMncStr)
.append(" mAlphaLong=").append(mAlphaLong)
@@ -248,6 +264,7 @@
dest.writeInt(mPci);
dest.writeInt(mTac);
dest.writeInt(mEarfcn);
+ dest.writeInt(mBandwidth);
dest.writeString(mAlphaLong);
dest.writeString(mAlphaShort);
}
@@ -259,6 +276,7 @@
mPci = in.readInt();
mTac = in.readInt();
mEarfcn = in.readInt();
+ mBandwidth = in.readInt();
mAlphaLong = in.readString();
mAlphaShort = in.readString();
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index b5e4eef..9232ed7 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -16,8 +16,11 @@
package android.telephony;
+import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Immutable cell information from a point in time.
@@ -47,6 +50,34 @@
/** @hide */
public static final int TIMESTAMP_TYPE_JAVA_RIL = 4;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ CONNECTION_NONE,
+ CONNECTION_PRIMARY_SERVING,
+ CONNECTION_SECONDARY_SERVING,
+ CONNECTION_UNKNOWN
+ })
+ public @interface CellConnectionStatus {}
+
+ /**
+ * Cell is not a serving cell.
+ *
+ * <p>The cell has been measured but is neither a camped nor serving cell (3GPP 36.304).
+ */
+ public static final int CONNECTION_NONE = 0;
+
+ /** UE is connected to cell for signalling and possibly data (3GPP 36.331, 25.331). */
+ public static final int CONNECTION_PRIMARY_SERVING = 1;
+
+ /** UE is connected to cell for data (3GPP 36.331, 25.331). */
+ public static final int CONNECTION_SECONDARY_SERVING = 2;
+
+ /** Connection status is unknown. */
+ public static final int CONNECTION_UNKNOWN = Integer.MAX_VALUE;
+
+ private int mCellConnectionStatus = CONNECTION_NONE;
+
// True if device is mRegistered to the mobile network
private boolean mRegistered;
@@ -69,6 +100,7 @@
this.mRegistered = ci.mRegistered;
this.mTimeStampType = ci.mTimeStampType;
this.mTimeStamp = ci.mTimeStamp;
+ this.mCellConnectionStatus = ci.mCellConnectionStatus;
}
/** True if this cell is registered to the mobile network */
@@ -90,6 +122,25 @@
}
/**
+ * Gets the connection status of this cell.
+ *
+ * @see #CONNECTION_NONE
+ * @see #CONNECTION_PRIMARY_SERVING
+ * @see #CONNECTION_SECONDARY_SERVING
+ * @see #CONNECTION_UNKNOWN
+ *
+ * @return The connection status of the cell.
+ */
+ @CellConnectionStatus
+ public int getCellConnectionStatus() {
+ return mCellConnectionStatus;
+ }
+ /** @hide */
+ public void setCellConnectionStatus(@CellConnectionStatus int cellConnectionStatus) {
+ mCellConnectionStatus = cellConnectionStatus;
+ }
+
+ /**
* Where time stamp gets recorded.
* @return one of TIMESTAMP_TYPE_XXXX
*
@@ -111,7 +162,7 @@
public int hashCode() {
int primeNum = 31;
return ((mRegistered ? 0 : 1) * primeNum) + ((int)(mTimeStamp / 1000) * primeNum)
- + (mTimeStampType * primeNum);
+ + (mTimeStampType * primeNum) + (mCellConnectionStatus * primeNum);
}
@Override
@@ -125,7 +176,9 @@
try {
CellInfo o = (CellInfo) other;
return mRegistered == o.mRegistered
- && mTimeStamp == o.mTimeStamp && mTimeStampType == o.mTimeStampType;
+ && mTimeStamp == o.mTimeStamp
+ && mTimeStampType == o.mTimeStampType
+ && mCellConnectionStatus == o.mCellConnectionStatus;
} catch (ClassCastException e) {
return false;
}
@@ -155,6 +208,7 @@
timeStampType = timeStampTypeToString(mTimeStampType);
sb.append(" mTimeStampType=").append(timeStampType);
sb.append(" mTimeStamp=").append(mTimeStamp).append("ns");
+ sb.append(" mCellConnectionStatus=").append(mCellConnectionStatus);
return sb.toString();
}
@@ -181,6 +235,7 @@
dest.writeInt(mRegistered ? 1 : 0);
dest.writeInt(mTimeStampType);
dest.writeLong(mTimeStamp);
+ dest.writeInt(mCellConnectionStatus);
}
/**
@@ -192,6 +247,7 @@
mRegistered = (in.readInt() == 1) ? true : false;
mTimeStampType = in.readInt();
mTimeStamp = in.readLong();
+ mCellConnectionStatus = in.readInt();
}
/** Implement the Parcelable interface */
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 56e1e64..4fa304a 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -310,6 +310,13 @@
* {@hide}
*/
public static final int DIAL_VIDEO_MODIFIED_TO_DIAL_VIDEO = 70;
+
+ /**
+ * The network has reported that an alternative emergency number has been dialed, but the user
+ * must exit airplane mode to place the call.
+ */
+ public static final int IMS_SIP_ALTERNATE_EMERGENCY_CALL = 71;
+
//*********************************************************************************************
// When adding a disconnect type:
// 1) Update toString() with the newly added disconnect type.
@@ -462,6 +469,8 @@
return "EMERGENCY_PERM_FAILURE";
case NORMAL_UNSPECIFIED:
return "NORMAL_UNSPECIFIED";
+ case IMS_SIP_ALTERNATE_EMERGENCY_CALL:
+ return "IMS_SIP_ALTERNATE_EMERGENCY_CALL";
default:
return "INVALID: " + cause;
}
diff --git a/telephony/java/android/telephony/INetworkService.aidl b/telephony/java/android/telephony/INetworkService.aidl
new file mode 100644
index 0000000..9ef7186
--- /dev/null
+++ b/telephony/java/android/telephony/INetworkService.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017 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.telephony;
+
+import android.telephony.INetworkServiceCallback;
+
+/**
+ * {@hide}
+ */
+oneway interface INetworkService
+{
+ void createNetworkServiceProvider(int slotId);
+ void removeNetworkServiceProvider(int slotId);
+ void getNetworkRegistrationState(int slotId, int domain, INetworkServiceCallback callback);
+ void registerForNetworkRegistrationStateChanged(int slotId, INetworkServiceCallback callback);
+ void unregisterForNetworkRegistrationStateChanged(int slotId, INetworkServiceCallback callback);
+}
diff --git a/telephony/java/android/telephony/INetworkServiceCallback.aidl b/telephony/java/android/telephony/INetworkServiceCallback.aidl
new file mode 100644
index 0000000..520598f
--- /dev/null
+++ b/telephony/java/android/telephony/INetworkServiceCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017 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.telephony;
+
+import android.telephony.NetworkRegistrationState;
+
+/**
+ * Network service call back interface
+ * @hide
+ */
+oneway interface INetworkServiceCallback
+{
+ void onGetNetworkRegistrationStateComplete(int result, in NetworkRegistrationState state);
+ void onNetworkStateChanged();
+}
diff --git a/telephony/java/android/telephony/NetworkRegistrationState.aidl b/telephony/java/android/telephony/NetworkRegistrationState.aidl
new file mode 100644
index 0000000..98cba77
--- /dev/null
+++ b/telephony/java/android/telephony/NetworkRegistrationState.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2017 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.telephony;
+
+parcelable NetworkRegistrationState;
diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java
new file mode 100644
index 0000000..e051069
--- /dev/null
+++ b/telephony/java/android/telephony/NetworkRegistrationState.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2017 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.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Description of a mobile network registration state
+ * @hide
+ */
+@SystemApi
+public class NetworkRegistrationState implements Parcelable {
+ /**
+ * Network domain
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "DOMAIN_", value = {DOMAIN_CS, DOMAIN_PS})
+ public @interface Domain {}
+
+ /** Circuit switching domain */
+ public static final int DOMAIN_CS = 1;
+ /** Packet switching domain */
+ public static final int DOMAIN_PS = 2;
+
+ /**
+ * Registration state
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "REG_STATE_",
+ value = {REG_STATE_NOT_REG_NOT_SEARCHING, REG_STATE_HOME, REG_STATE_NOT_REG_SEARCHING,
+ REG_STATE_DENIED, REG_STATE_UNKNOWN, REG_STATE_ROAMING})
+ public @interface RegState {}
+
+ /** Not registered. The device is not currently searching a new operator to register */
+ public static final int REG_STATE_NOT_REG_NOT_SEARCHING = 0;
+ /** Registered on home network */
+ public static final int REG_STATE_HOME = 1;
+ /** Not registered. The device is currently searching a new operator to register */
+ public static final int REG_STATE_NOT_REG_SEARCHING = 2;
+ /** Registration denied */
+ public static final int REG_STATE_DENIED = 3;
+ /** Registration state is unknown */
+ public static final int REG_STATE_UNKNOWN = 4;
+ /** Registered on roaming network */
+ public static final int REG_STATE_ROAMING = 5;
+
+ /**
+ * Supported service type
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "SERVICE_TYPE_",
+ value = {SERVICE_TYPE_VOICE, SERVICE_TYPE_DATA, SERVICE_TYPE_SMS, SERVICE_TYPE_VIDEO,
+ SERVICE_TYPE_EMERGENCY})
+ public @interface ServiceType {}
+
+ public static final int SERVICE_TYPE_VOICE = 1;
+ public static final int SERVICE_TYPE_DATA = 2;
+ public static final int SERVICE_TYPE_SMS = 3;
+ public static final int SERVICE_TYPE_VIDEO = 4;
+ public static final int SERVICE_TYPE_EMERGENCY = 5;
+
+ /** {@link AccessNetworkConstants.TransportType}*/
+ private final int mTransportType;
+
+ @Domain
+ private final int mDomain;
+
+ @RegState
+ private final int mRegState;
+
+ private final int mAccessNetworkTechnology;
+
+ private final int mReasonForDenial;
+
+ private final boolean mEmergencyOnly;
+
+ private final int[] mAvailableServices;
+
+ @Nullable
+ private final CellIdentity mCellIdentity;
+
+
+ /**
+ * @param transportType Transport type. Must be {@link AccessNetworkConstants.TransportType}
+ * @param domain Network domain. Must be DOMAIN_CS or DOMAIN_PS.
+ * @param regState Network registration state.
+ * @param accessNetworkTechnology See TelephonyManager NETWORK_TYPE_XXXX.
+ * @param reasonForDenial Reason for denial if the registration state is DENIED.
+ * @param availableServices The supported service.
+ * @param cellIdentity The identity representing a unique cell
+ */
+ public NetworkRegistrationState(int transportType, int domain, int regState,
+ int accessNetworkTechnology, int reasonForDenial, boolean emergencyOnly,
+ int[] availableServices, @Nullable CellIdentity cellIdentity) {
+ mTransportType = transportType;
+ mDomain = domain;
+ mRegState = regState;
+ mAccessNetworkTechnology = accessNetworkTechnology;
+ mReasonForDenial = reasonForDenial;
+ mAvailableServices = availableServices;
+ mCellIdentity = cellIdentity;
+ mEmergencyOnly = emergencyOnly;
+ }
+
+ protected NetworkRegistrationState(Parcel source) {
+ mTransportType = source.readInt();
+ mDomain = source.readInt();
+ mRegState = source.readInt();
+ mAccessNetworkTechnology = source.readInt();
+ mReasonForDenial = source.readInt();
+ mEmergencyOnly = source.readBoolean();
+ mAvailableServices = source.createIntArray();
+ mCellIdentity = source.readParcelable(CellIdentity.class.getClassLoader());
+ }
+
+ /**
+ * @return The transport type.
+ */
+ public int getTransportType() { return mTransportType; }
+
+ /**
+ * @return The network domain.
+ */
+ public @Domain int getDomain() { return mDomain; }
+
+ /**
+ * @return The registration state.
+ */
+ public @RegState int getRegState() {
+ return mRegState;
+ }
+
+ /**
+ * @return Whether emergency is enabled.
+ */
+ public boolean isEmergencyEnabled() { return mEmergencyOnly; }
+
+ /**
+ * @return List of available service types.
+ */
+ public int[] getAvailableServices() { return mAvailableServices; }
+
+ /**
+ * @return The access network technology. Must be one of TelephonyManager.NETWORK_TYPE_XXXX.
+ */
+ public int getAccessNetworkTechnology() {
+ return mAccessNetworkTechnology;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private static String regStateToString(int regState) {
+ switch (regState) {
+ case REG_STATE_NOT_REG_NOT_SEARCHING: return "NOT_REG_NOT_SEARCHING";
+ case REG_STATE_HOME: return "HOME";
+ case REG_STATE_NOT_REG_SEARCHING: return "NOT_REG_SEARCHING";
+ case REG_STATE_DENIED: return "DENIED";
+ case REG_STATE_UNKNOWN: return "UNKNOWN";
+ case REG_STATE_ROAMING: return "ROAMING";
+ }
+ return "Unknown reg state " + regState;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("NetworkRegistrationState{")
+ .append("transportType=").append(mTransportType)
+ .append(" domain=").append((mDomain == DOMAIN_CS) ? "CS" : "PS")
+ .append(" regState=").append(regStateToString(mRegState))
+ .append(" accessNetworkTechnology=")
+ .append(TelephonyManager.getNetworkTypeName(mAccessNetworkTechnology))
+ .append(" reasonForDenial=").append(mReasonForDenial)
+ .append(" emergencyEnabled=").append(mEmergencyOnly)
+ .append(" supportedServices=").append(mAvailableServices)
+ .append(" cellIdentity=").append(mCellIdentity)
+ .append("}").toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTransportType, mDomain, mRegState, mAccessNetworkTechnology,
+ mReasonForDenial, mEmergencyOnly, mAvailableServices, mCellIdentity);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o == null || !(o instanceof NetworkRegistrationState)) {
+ return false;
+ }
+
+ NetworkRegistrationState other = (NetworkRegistrationState) o;
+ return mTransportType == other.mTransportType
+ && mDomain == other.mDomain
+ && mRegState == other.mRegState
+ && mAccessNetworkTechnology == other.mAccessNetworkTechnology
+ && mReasonForDenial == other.mReasonForDenial
+ && mEmergencyOnly == other.mEmergencyOnly
+ && (mAvailableServices == other.mAvailableServices
+ || Arrays.equals(mAvailableServices, other.mAvailableServices))
+ && mCellIdentity == other.mCellIdentity;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTransportType);
+ dest.writeInt(mDomain);
+ dest.writeInt(mRegState);
+ dest.writeInt(mAccessNetworkTechnology);
+ dest.writeInt(mReasonForDenial);
+ dest.writeBoolean(mEmergencyOnly);
+ dest.writeIntArray(mAvailableServices);
+ dest.writeParcelable(mCellIdentity, 0);
+ }
+
+ public static final Parcelable.Creator<NetworkRegistrationState> CREATOR =
+ new Parcelable.Creator<NetworkRegistrationState>() {
+ @Override
+ public NetworkRegistrationState createFromParcel(Parcel source) {
+ return new NetworkRegistrationState(source);
+ }
+
+ @Override
+ public NetworkRegistrationState[] newArray(int size) {
+ return new NetworkRegistrationState[size];
+ }
+ };
+}
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
new file mode 100644
index 0000000..94921de
--- /dev/null
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright 2017 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.telephony;
+
+import android.annotation.CallSuper;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base class of network service. Services that extend NetworkService must register the service in
+ * their AndroidManifest to be detected by the framework. They must be protected by the permission
+ * "android.permission.BIND_NETWORK_SERVICE". The network service definition in the manifest must
+ * follow the following format:
+ * ...
+ * <service android:name=".xxxNetworkService"
+ * android:permission="android.permission.BIND_NETWORK_SERVICE" >
+ * <intent-filter>
+ * <action android:name="android.telephony.NetworkService" />
+ * </intent-filter>
+ * </service>
+ * @hide
+ */
+@SystemApi
+public abstract class NetworkService extends Service {
+
+ private final String TAG = NetworkService.class.getSimpleName();
+
+ public static final String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService";
+ public static final String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID";
+
+ private static final int NETWORK_SERVICE_CREATE_NETWORK_SERVICE_PROVIDER = 1;
+ private static final int NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER = 2;
+ private static final int NETWORK_SERVICE_REMOVE_ALL_NETWORK_SERVICE_PROVIDERS = 3;
+ private static final int NETWORK_SERVICE_GET_REGISTRATION_STATE = 4;
+ private static final int NETWORK_SERVICE_REGISTER_FOR_STATE_CHANGE = 5;
+ private static final int NETWORK_SERVICE_UNREGISTER_FOR_STATE_CHANGE = 6;
+ private static final int NETWORK_SERVICE_INDICATION_NETWORK_STATE_CHANGED = 7;
+
+
+ private final HandlerThread mHandlerThread;
+
+ private final NetworkServiceHandler mHandler;
+
+ private final SparseArray<NetworkServiceProvider> mServiceMap = new SparseArray<>();
+
+ private final INetworkServiceWrapper mBinder = new INetworkServiceWrapper();
+
+ /**
+ * The abstract class of the actual network service implementation. The network service provider
+ * must extend this class to support network connection. Note that each instance of network
+ * service is associated with one physical SIM slot.
+ */
+ public class NetworkServiceProvider {
+ private final int mSlotId;
+
+ private final List<INetworkServiceCallback>
+ mNetworkRegistrationStateChangedCallbacks = new ArrayList<>();
+
+ public NetworkServiceProvider(int slotId) {
+ mSlotId = slotId;
+ }
+
+ /**
+ * @return SIM slot id the network service associated with.
+ */
+ public final int getSlotId() {
+ return mSlotId;
+ }
+
+ /**
+ * API to get network registration state. The result will be passed to the callback.
+ * @param domain
+ * @param callback
+ * @return SIM slot id the network service associated with.
+ */
+ public void getNetworkRegistrationState(int domain, NetworkServiceCallback callback) {
+ callback.onGetNetworkRegistrationStateComplete(
+ NetworkServiceCallback.RESULT_ERROR_UNSUPPORTED, null);
+ }
+
+ public final void notifyNetworkRegistrationStateChanged() {
+ mHandler.obtainMessage(NETWORK_SERVICE_INDICATION_NETWORK_STATE_CHANGED,
+ mSlotId, 0, null).sendToTarget();
+ }
+
+ private void registerForStateChanged(INetworkServiceCallback callback) {
+ synchronized (mNetworkRegistrationStateChangedCallbacks) {
+ mNetworkRegistrationStateChangedCallbacks.add(callback);
+ }
+ }
+
+ private void unregisterForStateChanged(INetworkServiceCallback callback) {
+ synchronized (mNetworkRegistrationStateChangedCallbacks) {
+ mNetworkRegistrationStateChangedCallbacks.remove(callback);
+ }
+ }
+
+ private void notifyStateChangedToCallbacks() {
+ for (INetworkServiceCallback callback : mNetworkRegistrationStateChangedCallbacks) {
+ try {
+ callback.onNetworkStateChanged();
+ } catch (RemoteException exception) {
+ // Doing nothing.
+ }
+ }
+ }
+
+ /**
+ * Called when the instance of network service is destroyed (e.g. got unbind or binder died).
+ */
+ @CallSuper
+ protected void onDestroy() {
+ mNetworkRegistrationStateChangedCallbacks.clear();
+ }
+ }
+
+ private class NetworkServiceHandler extends Handler {
+
+ NetworkServiceHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ final int slotId = message.arg1;
+ final INetworkServiceCallback callback = (INetworkServiceCallback) message.obj;
+
+ NetworkServiceProvider serviceProvider = mServiceMap.get(slotId);
+
+ switch (message.what) {
+ case NETWORK_SERVICE_CREATE_NETWORK_SERVICE_PROVIDER:
+ // If the service provider doesn't exist yet, we try to create it.
+ if (serviceProvider == null) {
+ mServiceMap.put(slotId, createNetworkServiceProvider(slotId));
+ }
+ break;
+ case NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER:
+ // If the service provider doesn't exist yet, we try to create it.
+ if (serviceProvider != null) {
+ serviceProvider.onDestroy();
+ mServiceMap.remove(slotId);
+ }
+ break;
+ case NETWORK_SERVICE_REMOVE_ALL_NETWORK_SERVICE_PROVIDERS:
+ for (int i = 0; i < mServiceMap.size(); i++) {
+ serviceProvider = mServiceMap.get(i);
+ if (serviceProvider != null) {
+ serviceProvider.onDestroy();
+ }
+ }
+ mServiceMap.clear();
+ break;
+ case NETWORK_SERVICE_GET_REGISTRATION_STATE:
+ if (serviceProvider == null) break;
+ int domainId = message.arg2;
+ serviceProvider.getNetworkRegistrationState(domainId,
+ new NetworkServiceCallback(callback));
+
+ break;
+ case NETWORK_SERVICE_REGISTER_FOR_STATE_CHANGE:
+ if (serviceProvider == null) break;
+ serviceProvider.registerForStateChanged(callback);
+ break;
+ case NETWORK_SERVICE_UNREGISTER_FOR_STATE_CHANGE:
+ if (serviceProvider == null) break;
+ serviceProvider.unregisterForStateChanged(callback);
+ break;
+ case NETWORK_SERVICE_INDICATION_NETWORK_STATE_CHANGED:
+ if (serviceProvider == null) break;
+ serviceProvider.notifyStateChangedToCallbacks();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /** @hide */
+ protected NetworkService() {
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+
+ mHandler = new NetworkServiceHandler(mHandlerThread.getLooper());
+ log("network service created");
+ }
+
+ /**
+ * Create the instance of {@link NetworkServiceProvider}. Network service provider must override
+ * this method to facilitate the creation of {@link NetworkServiceProvider} instances. The system
+ * will call this method after binding the network service for each active SIM slot id.
+ *
+ * @param slotId SIM slot id the network service associated with.
+ * @return Network service object
+ */
+ protected abstract NetworkServiceProvider createNetworkServiceProvider(int slotId);
+
+ /** @hide */
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (intent == null || !NETWORK_SERVICE_INTERFACE.equals(intent.getAction())) {
+ loge("Unexpected intent " + intent);
+ return null;
+ }
+
+ return mBinder;
+ }
+
+ /** @hide */
+ @Override
+ public boolean onUnbind(Intent intent) {
+ mHandler.obtainMessage(NETWORK_SERVICE_REMOVE_ALL_NETWORK_SERVICE_PROVIDERS, 0,
+ 0, null).sendToTarget();
+
+ return false;
+ }
+
+ /** @hide */
+ @Override
+ public void onDestroy() {
+ mHandlerThread.quit();
+ }
+
+ /**
+ * A wrapper around INetworkService that forwards calls to implementations of
+ * {@link NetworkService}.
+ */
+ private class INetworkServiceWrapper extends INetworkService.Stub {
+
+ @Override
+ public void createNetworkServiceProvider(int slotId) {
+ mHandler.obtainMessage(NETWORK_SERVICE_CREATE_NETWORK_SERVICE_PROVIDER, slotId,
+ 0, null).sendToTarget();
+ }
+
+ @Override
+ public void removeNetworkServiceProvider(int slotId) {
+ mHandler.obtainMessage(NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER, slotId,
+ 0, null).sendToTarget();
+ }
+
+ @Override
+ public void getNetworkRegistrationState(
+ int slotId, int domain, INetworkServiceCallback callback) {
+ mHandler.obtainMessage(NETWORK_SERVICE_GET_REGISTRATION_STATE, slotId,
+ domain, callback).sendToTarget();
+ }
+
+ @Override
+ public void registerForNetworkRegistrationStateChanged(
+ int slotId, INetworkServiceCallback callback) {
+ mHandler.obtainMessage(NETWORK_SERVICE_REGISTER_FOR_STATE_CHANGE, slotId,
+ 0, callback).sendToTarget();
+ }
+
+ @Override
+ public void unregisterForNetworkRegistrationStateChanged(
+ int slotId,INetworkServiceCallback callback) {
+ mHandler.obtainMessage(NETWORK_SERVICE_UNREGISTER_FOR_STATE_CHANGE, slotId,
+ 0, callback).sendToTarget();
+ }
+ }
+
+ private final void log(String s) {
+ Rlog.d(TAG, s);
+ }
+
+ private final void loge(String s) {
+ Rlog.e(TAG, s);
+ }
+}
diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java
new file mode 100644
index 0000000..92ebf36
--- /dev/null
+++ b/telephony/java/android/telephony/NetworkServiceCallback.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 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.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.SystemApi;
+import android.os.RemoteException;
+import android.telephony.NetworkService.NetworkServiceProvider;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+
+/**
+ * Network service callback. Object of this class is passed to NetworkServiceProvider upon
+ * calling getNetworkRegistrationState, to receive asynchronous feedback from NetworkServiceProvider
+ * upon onGetNetworkRegistrationStateComplete. It's like a wrapper of INetworkServiceCallback
+ * because INetworkServiceCallback can't be a parameter type in public APIs.
+ *
+ * @hide
+ */
+@SystemApi
+public class NetworkServiceCallback {
+
+ private static final String mTag = NetworkServiceCallback.class.getSimpleName();
+
+ /**
+ * Result of network requests
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({RESULT_SUCCESS, RESULT_ERROR_UNSUPPORTED, RESULT_ERROR_INVALID_ARG, RESULT_ERROR_BUSY,
+ RESULT_ERROR_ILLEGAL_STATE, RESULT_ERROR_FAILED})
+ public @interface Result {}
+
+ /** Request is completed successfully */
+ public static final int RESULT_SUCCESS = 0;
+ /** Request is not support */
+ public static final int RESULT_ERROR_UNSUPPORTED = 1;
+ /** Request contains invalid arguments */
+ public static final int RESULT_ERROR_INVALID_ARG = 2;
+ /** Service is busy */
+ public static final int RESULT_ERROR_BUSY = 3;
+ /** Request sent in illegal state */
+ public static final int RESULT_ERROR_ILLEGAL_STATE = 4;
+ /** Request failed */
+ public static final int RESULT_ERROR_FAILED = 5;
+
+ private final WeakReference<INetworkServiceCallback> mCallback;
+
+ /** @hide */
+ public NetworkServiceCallback(INetworkServiceCallback callback) {
+ mCallback = new WeakReference<>(callback);
+ }
+
+ /**
+ * Called to indicate result of
+ * {@link NetworkServiceProvider#getNetworkRegistrationState(int, NetworkServiceCallback)}
+ *
+ * @param result Result status like {@link NetworkServiceCallback#RESULT_SUCCESS} or
+ * {@link NetworkServiceCallback#RESULT_ERROR_UNSUPPORTED}
+ * @param state The state information to be returned to callback.
+ */
+ public void onGetNetworkRegistrationStateComplete(int result, NetworkRegistrationState state) {
+ INetworkServiceCallback callback = mCallback.get();
+ if (callback != null) {
+ try {
+ callback.onGetNetworkRegistrationStateComplete(result, state);
+ } catch (RemoteException e) {
+ Rlog.e(mTag, "Failed to onGetNetworkRegistrationStateComplete on the remote");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index d4b4b88..90a3677 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -17,6 +17,8 @@
package android.telephony;
import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -24,6 +26,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* Contains phone state and service related information.
@@ -32,6 +38,7 @@
*
* <ul>
* <li>Service state: IN_SERVICE, OUT_OF_SERVICE, EMERGENCY_ONLY, POWER_OFF
+ * <li>Duplex mode: UNKNOWN, FDD, TDD
* <li>Roaming indicator
* <li>Operator name, short name and numeric id
* <li>Network selection mode
@@ -67,6 +74,26 @@
*/
public static final int STATE_POWER_OFF = 3;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({DUPLEX_MODE_UNKNOWN, DUPLEX_MODE_FDD, DUPLEX_MODE_TDD})
+ public @interface DuplexMode {}
+
+ /**
+ * Duplex mode for the phone is unknown.
+ */
+ public static final int DUPLEX_MODE_UNKNOWN = 0;
+
+ /**
+ * Duplex mode for the phone is frequency-division duplexing.
+ */
+ public static final int DUPLEX_MODE_FDD = 1;
+
+ /**
+ * Duplex mode for the phone is time-division duplexing.
+ */
+ public static final int DUPLEX_MODE_TDD = 2;
+
/**
* RIL level registration state values from ril.h
* ((const char **)response)[0] is registration state 0-6,
@@ -282,10 +309,15 @@
private boolean mIsUsingCarrierAggregation;
+ private int mChannelNumber;
+ private int[] mCellBandwidths = new int[0];
+
/* EARFCN stands for E-UTRA Absolute Radio Frequency Channel Number,
* Reference: 3GPP TS 36.104 5.4.3 */
private int mLteEarfcnRsrpBoost = 0;
+ private List<NetworkRegistrationState> mNetworkRegistrationStates = new ArrayList<>();
+
/**
* get String description of roaming type
* @hide
@@ -366,6 +398,7 @@
mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration;
mIsUsingCarrierAggregation = s.mIsUsingCarrierAggregation;
mLteEarfcnRsrpBoost = s.mLteEarfcnRsrpBoost;
+ mNetworkRegistrationStates = new ArrayList<>(s.mNetworkRegistrationStates);
}
/**
@@ -396,6 +429,10 @@
mIsDataRoamingFromRegistration = in.readInt() != 0;
mIsUsingCarrierAggregation = in.readInt() != 0;
mLteEarfcnRsrpBoost = in.readInt();
+ mNetworkRegistrationStates = new ArrayList<>();
+ in.readList(mNetworkRegistrationStates, NetworkRegistrationState.class.getClassLoader());
+ mChannelNumber = in.readInt();
+ mCellBandwidths = in.createIntArray();
}
public void writeToParcel(Parcel out, int flags) {
@@ -423,6 +460,9 @@
out.writeInt(mIsDataRoamingFromRegistration ? 1 : 0);
out.writeInt(mIsUsingCarrierAggregation ? 1 : 0);
out.writeInt(mLteEarfcnRsrpBoost);
+ out.writeList(mNetworkRegistrationStates);
+ out.writeInt(mChannelNumber);
+ out.writeIntArray(mCellBandwidths);
}
public int describeContents() {
@@ -476,6 +516,43 @@
}
/**
+ * Get the current duplex mode
+ *
+ * @see #DUPLEX_MODE_UNKNOWN
+ * @see #DUPLEX_MODE_FDD
+ * @see #DUPLEX_MODE_TDD
+ *
+ * @return Current {@code DuplexMode} for the phone
+ */
+ @DuplexMode
+ public int getDuplexMode() {
+ // TODO(b/72117602) determine duplex mode from channel number, using 3GPP 36.101 sections
+ // 5.7.3-1 and 5.5-1
+ return DUPLEX_MODE_UNKNOWN;
+ }
+
+ /**
+ * Get the channel number of the current primary serving cell, or -1 if unknown
+ *
+ * <p>This is EARFCN for LTE, UARFCN for UMTS, and ARFCN for GSM.
+ *
+ * @return Channel number of primary serving cell
+ */
+ public int getChannelNumber() {
+ return mChannelNumber;
+ }
+
+ /**
+ * Get an array of cell bandwidths (kHz) for the current serving cells
+ *
+ * @return Current serving cell bandwidths
+ */
+ @Nullable
+ public int[] getCellBandwidths() {
+ return mCellBandwidths;
+ }
+
+ /**
* Get current roaming indicator of phone
* (note: not just decoding from TS 27.007 7.2)
*
@@ -703,6 +780,8 @@
+ (mDataRegState * 37)
+ mVoiceRoamingType
+ mDataRoamingType
+ + mChannelNumber
+ + Arrays.hashCode(mCellBandwidths)
+ (mIsManualNetworkSelection ? 1 : 0)
+ ((null == mVoiceOperatorAlphaLong) ? 0 : mVoiceOperatorAlphaLong.hashCode())
+ ((null == mVoiceOperatorAlphaShort) ? 0 : mVoiceOperatorAlphaShort.hashCode())
@@ -735,6 +814,8 @@
&& mIsManualNetworkSelection == s.mIsManualNetworkSelection
&& mVoiceRoamingType == s.mVoiceRoamingType
&& mDataRoamingType == s.mDataRoamingType
+ && mChannelNumber == s.mChannelNumber
+ && Arrays.equals(mCellBandwidths, s.mCellBandwidths)
&& equalsHandlesNulls(mVoiceOperatorAlphaLong, s.mVoiceOperatorAlphaLong)
&& equalsHandlesNulls(mVoiceOperatorAlphaShort, s.mVoiceOperatorAlphaShort)
&& equalsHandlesNulls(mVoiceOperatorNumeric, s.mVoiceOperatorNumeric)
@@ -751,13 +832,14 @@
s.mCdmaDefaultRoamingIndicator)
&& mIsEmergencyOnly == s.mIsEmergencyOnly
&& mIsDataRoamingFromRegistration == s.mIsDataRoamingFromRegistration
- && mIsUsingCarrierAggregation == s.mIsUsingCarrierAggregation);
+ && mIsUsingCarrierAggregation == s.mIsUsingCarrierAggregation)
+ && mNetworkRegistrationStates.containsAll(s.mNetworkRegistrationStates);
}
/**
* Convert radio technology to String
*
- * @param radioTechnology
+ * @param rt radioTechnology
* @return String representation of the RAT
*
* @hide
@@ -863,6 +945,8 @@
.append("(" + rilServiceStateToString(mVoiceRegState) + ")")
.append(", mDataRegState=").append(mDataRegState)
.append("(" + rilServiceStateToString(mDataRegState) + ")")
+ .append(", mChannelNumber=").append(mChannelNumber)
+ .append(", mCellBandwidths=").append(Arrays.toString(mCellBandwidths))
.append(", mVoiceRoamingType=").append(getRoamingLogString(mVoiceRoamingType))
.append(", mDataRoamingType=").append(getRoamingLogString(mDataRoamingType))
.append(", mVoiceOperatorAlphaLong=").append(mVoiceOperatorAlphaLong)
@@ -884,6 +968,7 @@
.append(", mIsDataRoamingFromRegistration=").append(mIsDataRoamingFromRegistration)
.append(", mIsUsingCarrierAggregation=").append(mIsUsingCarrierAggregation)
.append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost)
+ .append(", mNetworkRegistrationStates=").append(mNetworkRegistrationStates)
.append("}").toString();
}
@@ -893,6 +978,8 @@
mDataRegState = state;
mVoiceRoamingType = ROAMING_TYPE_NOT_ROAMING;
mDataRoamingType = ROAMING_TYPE_NOT_ROAMING;
+ mChannelNumber = -1;
+ mCellBandwidths = new int[0];
mVoiceOperatorAlphaLong = null;
mVoiceOperatorAlphaShort = null;
mVoiceOperatorNumeric = null;
@@ -913,6 +1000,7 @@
mIsDataRoamingFromRegistration = false;
mIsUsingCarrierAggregation = false;
mLteEarfcnRsrpBoost = 0;
+ mNetworkRegistrationStates = new ArrayList<>();
}
public void setStateOutOfService() {
@@ -940,6 +1028,16 @@
if (VDBG) Rlog.d(LOG_TAG, "[ServiceState] setDataRegState=" + mDataRegState);
}
+ /** @hide */
+ public void setCellBandwidths(int[] bandwidths) {
+ mCellBandwidths = bandwidths;
+ }
+
+ /** @hide */
+ public void setChannelNumber(int channelNumber) {
+ mChannelNumber = channelNumber;
+ }
+
public void setRoaming(boolean roaming) {
mVoiceRoamingType = (roaming ? ROAMING_TYPE_UNKNOWN : ROAMING_TYPE_NOT_ROAMING);
mDataRoamingType = mVoiceRoamingType;
@@ -1088,6 +1186,8 @@
mIsDataRoamingFromRegistration = m.getBoolean("isDataRoamingFromRegistration");
mIsUsingCarrierAggregation = m.getBoolean("isUsingCarrierAggregation");
mLteEarfcnRsrpBoost = m.getInt("LteEarfcnRsrpBoost");
+ mChannelNumber = m.getInt("ChannelNumber");
+ mCellBandwidths = m.getIntArray("CellBandwidths");
}
/**
@@ -1119,6 +1219,8 @@
m.putBoolean("isDataRoamingFromRegistration", mIsDataRoamingFromRegistration);
m.putBoolean("isUsingCarrierAggregation", mIsUsingCarrierAggregation);
m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost);
+ m.putInt("ChannelNumber", mChannelNumber);
+ m.putIntArray("CellBandwidths", mCellBandwidths);
}
/** @hide */
@@ -1394,4 +1496,52 @@
return newSs;
}
+
+ /**
+ * Get all of the available network registration states.
+ *
+ * @return List of registration states
+ * @hide
+ */
+ @SystemApi
+ public List<NetworkRegistrationState> getNetworkRegistrationStates() {
+ return mNetworkRegistrationStates;
+ }
+
+ /**
+ * Get the network registration states with given transport type.
+ *
+ * @param transportType The transport type. See {@link AccessNetworkConstants.TransportType}
+ * @return List of registration states.
+ * @hide
+ */
+ @SystemApi
+ public List<NetworkRegistrationState> getNetworkRegistrationStates(int transportType) {
+ List<NetworkRegistrationState> list = new ArrayList<>();
+ for (NetworkRegistrationState networkRegistrationState : mNetworkRegistrationStates) {
+ if (networkRegistrationState.getTransportType() == transportType) {
+ list.add(networkRegistrationState);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Get the network registration states with given transport type and domain.
+ *
+ * @param transportType The transport type. See {@link AccessNetworkConstants.TransportType}
+ * @param domain The network domain. Must be DOMAIN_CS or DOMAIN_PS.
+ * @return The matching NetworkRegistrationState.
+ * @hide
+ */
+ @SystemApi
+ public NetworkRegistrationState getNetworkRegistrationStates(int transportType, int domain) {
+ for (NetworkRegistrationState networkRegistrationState : mNetworkRegistrationStates) {
+ if (networkRegistrationState.getTransportType() == transportType
+ && networkRegistrationState.getDomain() == domain) {
+ return networkRegistrationState;
+ }
+ }
+ return null;
+ }
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 5d88cf0..0874b86 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1134,7 +1134,11 @@
// SMS send failure result codes
- /** No error. {@hide}*/
+ /**
+ * No error.
+ * @hide
+ */
+ @SystemApi
static public final int RESULT_ERROR_NONE = 0;
/** Generic failure cause */
static public final int RESULT_ERROR_GENERIC_FAILURE = 1;
@@ -1146,12 +1150,113 @@
static public final int RESULT_ERROR_NO_SERVICE = 4;
/** Failed because we reached the sending queue limit. */
static public final int RESULT_ERROR_LIMIT_EXCEEDED = 5;
- /** Failed because FDN is enabled. {@hide} */
+ /**
+ * Failed because FDN is enabled.
+ * @hide
+ */
+ @SystemApi
static public final int RESULT_ERROR_FDN_CHECK_FAILURE = 6;
/** Failed because user denied the sending of this short code. */
static public final int RESULT_ERROR_SHORT_CODE_NOT_ALLOWED = 7;
/** Failed because the user has denied this app ever send premium short codes. */
static public final int RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED = 8;
+ /**
+ * Failed because the radio was not available
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_RADIO_NOT_AVAILABLE = 9;
+ /**
+ * Failed because of network rejection
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_NETWORK_REJECT = 10;
+ /**
+ * Failed because of invalid arguments
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_INVALID_ARGUMENTS = 11;
+ /**
+ * Failed because of an invalid state
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_INVALID_STATE = 12;
+ /**
+ * Failed because there is no memory
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_NO_MEMORY = 13;
+ /**
+ * Failed because the sms format is not valid
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_INVALID_SMS_FORMAT = 14;
+ /**
+ * Failed because of a system error
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_SYSTEM_ERROR = 15;
+ /**
+ * Failed because of a modem error
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_MODEM_ERROR = 16;
+ /**
+ * Failed because of a network error
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_NETWORK_ERROR = 17;
+ /**
+ * Failed because of an encoding error
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_ENCODING_ERROR = 18;
+ /**
+ * Failed because of an invalid smsc address
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_INVALID_SMSC_ADDRESS = 19;
+ /**
+ * Failed because the operation is not allowed
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_OPERATION_NOT_ALLOWED = 20;
+ /**
+ * Failed because of an internal error
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_INTERNAL_ERROR = 21;
+ /**
+ * Failed because there are no resources
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_NO_RESOURCES = 22;
+ /**
+ * Failed because the operation was cancelled
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_CANCELLED = 23;
+ /**
+ * Failed because the request is not supported
+ * @hide
+ */
+ @SystemApi
+ static public final int RESULT_REQUEST_NOT_SUPPORTED = 24;
+
static private final String PHONE_PACKAGE_NAME = "com.android.phone";
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2bdbfdd..0a6d960 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -22,14 +22,13 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
-import android.annotation.SuppressLint;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.WorkerThread;
import android.app.ActivityThread;
import android.app.PendingIntent;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
@@ -43,7 +42,6 @@
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemProperties;
-import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.service.carrier.CarrierIdentifier;
import android.telecom.PhoneAccount;
@@ -63,7 +61,6 @@
import com.android.internal.telephony.IPhoneSubInfo;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.ITelephonyRegistry;
-import com.android.internal.telephony.OperatorInfo;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.TelephonyProperties;
@@ -2601,6 +2598,53 @@
}
}
+ /**
+ * Gets all the UICC slots.
+ *
+ * @return UiccSlotInfo array.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public UiccSlotInfo[] getUiccSlotsInfo() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) {
+ return null;
+ }
+ return telephony.getUiccSlotsInfo();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Map logicalSlot to physicalSlot, and activate the physicalSlot if it is inactive. For
+ * example, passing the physicalSlots array [1, 0] means mapping the first item 1, which is
+ * physical slot index 1, to the logical slot 0; and mapping the second item 0, which is
+ * physical slot index 0, to the logical slot 1. The index of the array means the index of the
+ * logical slots.
+ *
+ * @param physicalSlots Index i in the array representing physical slot for phone i. The array
+ * size should be same as {@link #getPhoneCount()}.
+ * @return boolean Return true if the switch succeeds, false if the switch fails.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public boolean switchSlots(int[] physicalSlots) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) {
+ return false;
+ }
+ return telephony.switchSlots(physicalSlots);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
//
//
// Subscriber Info
diff --git a/telephony/java/android/telephony/UiccSlotInfo.aidl b/telephony/java/android/telephony/UiccSlotInfo.aidl
new file mode 100644
index 0000000..5571a6c
--- /dev/null
+++ b/telephony/java/android/telephony/UiccSlotInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018 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.telephony;
+
+parcelable UiccSlotInfo;
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
new file mode 100644
index 0000000..0b3cbad
--- /dev/null
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2018 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.telephony;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+import android.annotation.IntDef;
+
+/**
+ * Class for the information of a UICC slot.
+ * @hide
+ */
+@SystemApi
+public class UiccSlotInfo implements Parcelable {
+ /**
+ * Card state.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "CARD_STATE_INFO_" }, value = {
+ CARD_STATE_INFO_ABSENT,
+ CARD_STATE_INFO_PRESENT,
+ CARD_STATE_INFO_ERROR,
+ CARD_STATE_INFO_RESTRICTED
+ })
+ public @interface CardStateInfo {}
+
+ /** Card state absent. */
+ public static final int CARD_STATE_INFO_ABSENT = 1;
+
+ /** Card state present. */
+ public static final int CARD_STATE_INFO_PRESENT = 2;
+
+ /** Card state error. */
+ public static final int CARD_STATE_INFO_ERROR = 3;
+
+ /** Card state restricted. */
+ public static final int CARD_STATE_INFO_RESTRICTED = 4;
+
+ public final boolean isActive;
+ public final boolean isEuicc;
+ public final String cardId;
+ public final @CardStateInfo int cardStateInfo;
+
+ public static final Creator<UiccSlotInfo> CREATOR = new Creator<UiccSlotInfo>() {
+ @Override
+ public UiccSlotInfo createFromParcel(Parcel in) {
+ return new UiccSlotInfo(in);
+ }
+
+ @Override
+ public UiccSlotInfo[] newArray(int size) {
+ return new UiccSlotInfo[size];
+ }
+ };
+
+ private UiccSlotInfo(Parcel in) {
+ isActive = in.readByte() != 0;
+ isEuicc = in.readByte() != 0;
+ cardId = in.readString();
+ cardStateInfo = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeByte((byte) (isActive ? 1 : 0));
+ dest.writeByte((byte) (isEuicc ? 1 : 0));
+ dest.writeString(cardId);
+ dest.writeInt(cardStateInfo);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public UiccSlotInfo(boolean isActive, boolean isEuicc, String cardId,
+ @CardStateInfo int cardStateInfo) {
+ this.isActive = isActive;
+ this.isEuicc = isEuicc;
+ this.cardId = cardId;
+ this.cardStateInfo = cardStateInfo;
+ }
+
+ public boolean getIsActive() {
+ return isActive;
+ }
+
+ public boolean getIsEuicc() {
+ return isEuicc;
+ }
+
+ public String getCardId() {
+ return cardId;
+ }
+
+ @CardStateInfo
+ public int getCardStateInfo() {
+ return cardStateInfo;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ UiccSlotInfo that = (UiccSlotInfo) obj;
+ return (isActive == that.isActive)
+ && (isEuicc == that.isEuicc)
+ && (cardId == that.cardId)
+ && (cardStateInfo == that.cardStateInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + (isActive ? 1 : 0);
+ result = 31 * result + (isEuicc ? 1 : 0);
+ result = 31 * result + Objects.hashCode(cardId);
+ result = 31 * result + cardStateInfo;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "UiccSlotInfo (isActive="
+ + isActive
+ + ", isEuicc="
+ + isEuicc
+ + ", cardId="
+ + cardId
+ + ", cardState="
+ + cardStateInfo
+ + ")";
+ }
+}
diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
index 5197107..93c316f 100644
--- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
@@ -209,6 +209,13 @@
return MMTelFeature.this.getSmsFormat();
}
}
+
+ @Override
+ public void onSmsReady() {
+ synchronized (mLock) {
+ MMTelFeature.this.onSmsReady();
+ }
+ }
};
/**
@@ -384,25 +391,29 @@
return null;
}
- public void setSmsListener(IImsSmsListener listener) {
+ private void setSmsListener(IImsSmsListener listener) {
getSmsImplementation().registerSmsListener(listener);
}
- public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+ private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
byte[] pdu) {
getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu);
}
- public void acknowledgeSms(int token, int messageRef,
+ private void acknowledgeSms(int token, int messageRef,
@SmsImplBase.DeliverStatusResult int result) {
getSmsImplementation().acknowledgeSms(token, messageRef, result);
}
- public void acknowledgeSmsReport(int token, int messageRef,
+ private void acknowledgeSmsReport(int token, int messageRef,
@SmsImplBase.StatusReportResult int result) {
getSmsImplementation().acknowledgeSmsReport(token, messageRef, result);
}
+ private void onSmsReady() {
+ getSmsImplementation().onReady();
+ }
+
/**
* Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default
* non-functional implementation is returned.
diff --git a/telephony/java/android/telephony/ims/internal/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/SmsImplBase.java
deleted file mode 100644
index 33b23d9..0000000
--- a/telephony/java/android/telephony/ims/internal/SmsImplBase.java
+++ /dev/null
@@ -1,273 +0,0 @@
-/*
- * Copyright (C) 2017 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.telephony.ims.internal;
-
-import android.annotation.IntDef;
-import android.os.RemoteException;
-import android.telephony.SmsManager;
-import android.telephony.SmsMessage;
-import android.telephony.ims.internal.aidl.IImsSmsListener;
-import android.telephony.ims.internal.feature.MmTelFeature;
-import android.util.Log;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Base implementation for SMS over IMS.
- *
- * Any service wishing to provide SMS over IMS should extend this class and implement all methods
- * that the service supports.
- * @hide
- */
-public class SmsImplBase {
- private static final String LOG_TAG = "SmsImplBase";
-
- /** @hide */
- @IntDef({
- SEND_STATUS_OK,
- SEND_STATUS_ERROR,
- SEND_STATUS_ERROR_RETRY,
- SEND_STATUS_ERROR_FALLBACK
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface SendStatusResult {}
- /**
- * Message was sent successfully.
- */
- public static final int SEND_STATUS_OK = 1;
-
- /**
- * IMS provider failed to send the message and platform should not retry falling back to sending
- * the message using the radio.
- */
- public static final int SEND_STATUS_ERROR = 2;
-
- /**
- * IMS provider failed to send the message and platform should retry again after setting TP-RD bit
- * to high.
- */
- public static final int SEND_STATUS_ERROR_RETRY = 3;
-
- /**
- * IMS provider failed to send the message and platform should retry falling back to sending
- * the message using the radio.
- */
- public static final int SEND_STATUS_ERROR_FALLBACK = 4;
-
- /** @hide */
- @IntDef({
- DELIVER_STATUS_OK,
- DELIVER_STATUS_ERROR
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface DeliverStatusResult {}
- /**
- * Message was delivered successfully.
- */
- public static final int DELIVER_STATUS_OK = 1;
-
- /**
- * Message was not delivered.
- */
- public static final int DELIVER_STATUS_ERROR = 2;
-
- /** @hide */
- @IntDef({
- STATUS_REPORT_STATUS_OK,
- STATUS_REPORT_STATUS_ERROR
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface StatusReportResult {}
-
- /**
- * Status Report was set successfully.
- */
- public static final int STATUS_REPORT_STATUS_OK = 1;
-
- /**
- * Error while setting status report.
- */
- public static final int STATUS_REPORT_STATUS_ERROR = 2;
-
-
- // Lock for feature synchronization
- private final Object mLock = new Object();
- private IImsSmsListener mListener;
-
- /**
- * Registers a listener responsible for handling tasks like delivering messages.
- *
- * @param listener listener to register.
- *
- * @hide
- */
- public final void registerSmsListener(IImsSmsListener listener) {
- synchronized (mLock) {
- mListener = listener;
- }
- }
-
- /**
- * This method will be triggered by the platform when the user attempts to send an SMS. This
- * method should be implemented by the IMS providers to provide implementation of sending an SMS
- * over IMS.
- *
- * @param token unique token generated by the platform that should be used when triggering
- * callbacks for this specific message.
- * @param messageRef the message reference.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
- * @param smsc the Short Message Service Center address.
- * @param isRetry whether it is a retry of an already attempted message or not.
- * @param pdu PDUs representing the contents of the message.
- */
- public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
- byte[] pdu) {
- // Base implementation returns error. Should be overridden.
- try {
- onSendSmsResult(token, messageRef, SEND_STATUS_ERROR,
- SmsManager.RESULT_ERROR_GENERIC_FAILURE);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Can not send sms: " + e.getMessage());
- }
- }
-
- /**
- * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])}
- * has been called to deliver the result to the IMS provider.
- *
- * @param token token provided in {@link #onSmsReceived(int, String, byte[])}
- * @param result result of delivering the message. Valid values are defined in
- * {@link DeliverStatusResult}
- * @param messageRef the message reference
- */
- public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
- Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
- }
-
- /**
- * This method will be triggered by the platform after
- * {@link #onSmsStatusReportReceived(int, int, String, byte[])} has been called to provide the
- * result to the IMS provider.
- *
- * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param result result of delivering the message. Valid values are defined in
- * {@link StatusReportResult}
- * @param messageRef the message reference
- */
- public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
- Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
- }
-
- /**
- * This method should be triggered by the IMS providers when there is an incoming message. The
- * platform will deliver the message to the messages database and notify the IMS provider of the
- * result by calling {@link #acknowledgeSms(int, int, int)}.
- *
- * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
- *
- * @param token unique token generated by IMS providers that the platform will use to trigger
- * callbacks for this message.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
- * @param pdu PDUs representing the contents of the message.
- * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
- */
- public final void onSmsReceived(int token, String format, byte[] pdu)
- throws IllegalStateException {
- synchronized (mLock) {
- if (mListener == null) {
- throw new IllegalStateException("Feature not ready.");
- }
- try {
- mListener.onSmsReceived(token, format, pdu);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Can not deliver sms: " + e.getMessage());
- acknowledgeSms(token, 0, DELIVER_STATUS_ERROR);
- }
- }
- }
-
- /**
- * This method should be triggered by the IMS providers to pass the result of the sent message
- * to the platform.
- *
- * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
- *
- * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
- * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult}
- * @param reason reason in case status is failure. Valid values are:
- * {@link SmsManager#RESULT_ERROR_NONE},
- * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
- * {@link SmsManager#RESULT_ERROR_RADIO_OFF},
- * {@link SmsManager#RESULT_ERROR_NULL_PDU},
- * {@link SmsManager#RESULT_ERROR_NO_SERVICE},
- * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED}
- * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
- * @throws RemoteException if the connection to the framework is not available. If this happens
- * attempting to send the SMS should be aborted.
- */
- public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
- int reason) throws IllegalStateException, RemoteException {
- synchronized (mLock) {
- if (mListener == null) {
- throw new IllegalStateException("Feature not ready.");
- }
- mListener.onSendSmsResult(token, messageRef, status, reason);
- }
- }
-
- /**
- * Sets the status report of the sent message.
- *
- * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param messageRef the message reference.
- * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
- * @param pdu PDUs representing the content of the status report.
- * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
- */
- public final void onSmsStatusReportReceived(int token, int messageRef, String format,
- byte[] pdu) {
- synchronized (mLock) {
- if (mListener == null) {
- throw new IllegalStateException("Feature not ready.");
- }
- try {
- mListener.onSmsStatusReportReceived(token, messageRef, format, pdu);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Can not process sms status report: " + e.getMessage());
- acknowledgeSmsReport(token, messageRef, STATUS_REPORT_STATUS_ERROR);
- }
- }
- }
-
- /**
- * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
- * Provider.
- *
- * @return the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
- */
- public String getSmsFormat() {
- return SmsMessage.FORMAT_3GPP;
- }
-
-}
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
index 785113f..e226ada 100644
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
+++ b/telephony/java/android/telephony/ims/internal/aidl/IImsMmTelFeature.aidl
@@ -18,7 +18,6 @@
import android.os.Message;
import android.telephony.ims.internal.aidl.IImsMmTelListener;
-import android.telephony.ims.internal.aidl.IImsSmsListener;
import android.telephony.ims.internal.aidl.IImsCapabilityCallback;
import android.telephony.ims.internal.aidl.IImsCallSessionListener;
import android.telephony.ims.internal.feature.CapabilityChangeRequest;
@@ -50,11 +49,4 @@
IImsCapabilityCallback c);
oneway void queryCapabilityConfiguration(int capability, int radioTech,
IImsCapabilityCallback c);
- // SMS APIs
- void setSmsListener(IImsSmsListener l);
- oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry,
- in byte[] pdu);
- oneway void acknowledgeSms(int token, int messageRef, int result);
- oneway void acknowledgeSmsReport(int token, int messageRef, int result);
- String getSmsFormat();
}
diff --git a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl b/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
deleted file mode 100644
index bf8d90b..0000000
--- a/telephony/java/android/telephony/ims/internal/aidl/IImsSmsListener.aidl
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (c) 2017 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.telephony.ims.internal.aidl;
-
-/**
- * See MMTelFeature for more information.
- * {@hide}
- */
-interface IImsSmsListener {
- void onSendSmsResult(in int token, in int messageRef, in int status, in int reason);
- void onSmsStatusReportReceived(in int token, in int messageRef, in String format,
- in byte[] pdu);
- void onSmsReceived(in int token, in String format, in byte[] pdu);
-}
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
index 8d888c2..9b576c7 100644
--- a/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/internal/feature/MmTelFeature.java
@@ -21,14 +21,10 @@
import android.os.RemoteException;
import android.telecom.TelecomManager;
import android.telephony.ims.internal.ImsCallSessionListener;
-import android.telephony.ims.internal.SmsImplBase;
-import android.telephony.ims.internal.SmsImplBase.DeliverStatusResult;
-import android.telephony.ims.internal.SmsImplBase.StatusReportResult;
import android.telephony.ims.internal.aidl.IImsCallSessionListener;
import android.telephony.ims.internal.aidl.IImsCapabilityCallback;
import android.telephony.ims.internal.aidl.IImsMmTelFeature;
import android.telephony.ims.internal.aidl.IImsMmTelListener;
-import android.telephony.ims.internal.aidl.IImsSmsListener;
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.telephony.ims.stub.ImsEcbmImplBase;
import android.telephony.ims.stub.ImsMultiEndpointImplBase;
@@ -68,11 +64,6 @@
}
@Override
- public void setSmsListener(IImsSmsListener l) throws RemoteException {
- MmTelFeature.this.setSmsListener(l);
- }
-
- @Override
public int getFeatureState() throws RemoteException {
synchronized (mLock) {
return MmTelFeature.this.getFeatureState();
@@ -152,35 +143,6 @@
IImsCapabilityCallback c) {
queryCapabilityConfigurationInternal(capability, radioTech, c);
}
-
- @Override
- public void sendSms(int token, int messageRef, String format, String smsc, boolean retry,
- byte[] pdu) {
- synchronized (mLock) {
- MmTelFeature.this.sendSms(token, messageRef, format, smsc, retry, pdu);
- }
- }
-
- @Override
- public void acknowledgeSms(int token, int messageRef, int result) {
- synchronized (mLock) {
- MmTelFeature.this.acknowledgeSms(token, messageRef, result);
- }
- }
-
- @Override
- public void acknowledgeSmsReport(int token, int messageRef, int result) {
- synchronized (mLock) {
- MmTelFeature.this.acknowledgeSmsReport(token, messageRef, result);
- }
- }
-
- @Override
- public String getSmsFormat() {
- synchronized (mLock) {
- return MmTelFeature.this.getSmsFormat();
- }
- }
};
/**
@@ -292,10 +254,6 @@
}
}
- private void setSmsListener(IImsSmsListener listener) {
- getSmsImplementation().registerSmsListener(listener);
- }
-
private void queryCapabilityConfigurationInternal(int capability, int radioTech,
IImsCapabilityCallback c) {
boolean enabled = queryCapabilityConfiguration(capability, radioTech);
@@ -457,33 +415,6 @@
// Base Implementation - Should be overridden
}
- private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
- byte[] pdu) {
- getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu);
- }
-
- private void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
- getSmsImplementation().acknowledgeSms(token, messageRef, result);
- }
-
- private void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
- getSmsImplementation().acknowledgeSmsReport(token, messageRef, result);
- }
-
- private String getSmsFormat() {
- return getSmsImplementation().getSmsFormat();
- }
-
- /**
- * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default
- * non-functional implementation is returned.
- *
- * @return an instance of {@link SmsImplBase} which should be implemented by the IMS Provider.
- */
- protected SmsImplBase getSmsImplementation() {
- return new SmsImplBase();
- }
-
/**{@inheritDoc}*/
@Override
public void onFeatureRemoved() {
diff --git a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java
index 113dad4..89acc80 100644
--- a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java
+++ b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java
@@ -17,10 +17,10 @@
package android.telephony.ims.internal.stub;
import android.annotation.IntDef;
+import android.annotation.SystemApi;
import android.os.RemoteException;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
-import android.telephony.ims.internal.feature.MmTelFeature;
import android.util.Log;
import com.android.ims.internal.IImsSmsListener;
@@ -33,11 +33,14 @@
*
* Any service wishing to provide SMS over IMS should extend this class and implement all methods
* that the service supports.
+ *
* @hide
*/
+@SystemApi
public class SmsImplBase {
private static final String LOG_TAG = "SmsImplBase";
+ /** @hide */
@IntDef({
SEND_STATUS_OK,
SEND_STATUS_ERROR,
@@ -58,8 +61,8 @@
public static final int SEND_STATUS_ERROR = 2;
/**
- * IMS provider failed to send the message and platform should retry again after setting TP-RD bit
- * to high.
+ * IMS provider failed to send the message and platform should retry again after setting TP-RD
+ * bit to high.
*/
public static final int SEND_STATUS_ERROR_RETRY = 3;
@@ -69,6 +72,7 @@
*/
public static final int SEND_STATUS_ERROR_FALLBACK = 4;
+ /** @hide */
@IntDef({
DELIVER_STATUS_OK,
DELIVER_STATUS_ERROR
@@ -85,6 +89,7 @@
*/
public static final int DELIVER_STATUS_ERROR = 2;
+ /** @hide */
@IntDef({
STATUS_REPORT_STATUS_OK,
STATUS_REPORT_STATUS_ERROR
@@ -140,21 +145,23 @@
try {
onSendSmsResult(token, messageRef, SEND_STATUS_ERROR,
SmsManager.RESULT_ERROR_GENERIC_FAILURE);
- } catch (RemoteException e) {
+ } catch (RuntimeException e) {
Log.e(LOG_TAG, "Can not send sms: " + e.getMessage());
}
}
/**
- * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])}
- * has been called to deliver the result to the IMS provider.
+ * This method will be triggered by the platform after
+ * {@link #onSmsReceived(int, String, byte[])} has been called to deliver the result to the IMS
+ * provider.
*
* @param token token provided in {@link #onSmsReceived(int, String, byte[])}
- * @param result result of delivering the message. Valid values are defined in
- * {@link DeliverStatusResult}
+ * @param result result of delivering the message. Valid values are:
+ * {@link #DELIVER_STATUS_OK},
+ * {@link #DELIVER_STATUS_OK}
* @param messageRef the message reference
*/
- public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
+ public void acknowledgeSms(int token, @DeliverStatusResult int messageRef, int result) {
Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
}
@@ -164,8 +171,9 @@
* result to the IMS provider.
*
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
- * @param result result of delivering the message. Valid values are defined in
- * {@link StatusReportResult}
+ * @param result result of delivering the message. Valid values are:
+ * {@link #STATUS_REPORT_STATUS_OK},
+ * {@link #STATUS_REPORT_STATUS_ERROR}
* @param messageRef the message reference
*/
public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
@@ -177,20 +185,17 @@
* platform will deliver the message to the messages database and notify the IMS provider of the
* result by calling {@link #acknowledgeSms(int, int, int)}.
*
- * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
- *
* @param token unique token generated by IMS providers that the platform will use to trigger
* callbacks for this message.
* @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
* {@link SmsMessage#FORMAT_3GPP2}.
* @param pdu PDUs representing the contents of the message.
- * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+ * @throws RuntimeException if called before {@link #onReady()} is triggered.
*/
- public final void onSmsReceived(int token, String format, byte[] pdu)
- throws IllegalStateException {
+ public final void onSmsReceived(int token, String format, byte[] pdu) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
- throw new IllegalStateException("Feature not ready.");
+ throw new RuntimeException("Feature not ready.");
}
try {
mListener.onSmsReceived(token, format, pdu);
@@ -205,11 +210,13 @@
* This method should be triggered by the IMS providers to pass the result of the sent message
* to the platform.
*
- * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
- *
* @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
* @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
- * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult}
+ * @param status result of sending the SMS. Valid values are:
+ * {@link #SEND_STATUS_OK},
+ * {@link #SEND_STATUS_ERROR},
+ * {@link #SEND_STATUS_ERROR_RETRY},
+ * {@link #SEND_STATUS_ERROR_FALLBACK},
* @param reason reason in case status is failure. Valid values are:
* {@link SmsManager#RESULT_ERROR_NONE},
* {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
@@ -217,19 +224,41 @@
* {@link SmsManager#RESULT_ERROR_NULL_PDU},
* {@link SmsManager#RESULT_ERROR_NO_SERVICE},
* {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
+ * {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE},
* {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED}
- * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
- * @throws RemoteException if the connection to the framework is not available. If this happens
- * attempting to send the SMS should be aborted.
+ * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED},
+ * {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE},
+ * {@link SmsManager#RESULT_NETWORK_REJECT},
+ * {@link SmsManager#RESULT_INVALID_ARGUMENTS},
+ * {@link SmsManager#RESULT_INVALID_STATE},
+ * {@link SmsManager#RESULT_NO_MEMORY},
+ * {@link SmsManager#RESULT_INVALID_SMS_FORMAT},
+ * {@link SmsManager#RESULT_SYSTEM_ERROR},
+ * {@link SmsManager#RESULT_MODEM_ERROR},
+ * {@link SmsManager#RESULT_NETWORK_ERROR},
+ * {@link SmsManager#RESULT_ENCODING_ERROR},
+ * {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS},
+ * {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED},
+ * {@link SmsManager#RESULT_INTERNAL_ERROR},
+ * {@link SmsManager#RESULT_NO_RESOURCES},
+ * {@link SmsManager#RESULT_CANCELLED},
+ * {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED}
+ *
+ * @throws RuntimeException if called before {@link #onReady()} is triggered or if the
+ * connection to the framework is not available. If this happens attempting to send the SMS
+ * should be aborted.
*/
- public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
- int reason) throws IllegalStateException, RemoteException {
+ public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
+ int reason) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
- throw new IllegalStateException("Feature not ready.");
+ throw new RuntimeException("Feature not ready.");
}
- mListener.onSendSmsResult(token, messageRef, status, reason);
+ try {
+ mListener.onSendSmsResult(token, messageRef, status, reason);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
}
}
@@ -241,13 +270,13 @@
* @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
* {@link SmsMessage#FORMAT_3GPP2}.
* @param pdu PDUs representing the content of the status report.
- * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+ * @throws RuntimeException if called before {@link #onReady()} is triggered
*/
public final void onSmsStatusReportReceived(int token, int messageRef, String format,
- byte[] pdu) {
+ byte[] pdu) throws RuntimeException{
synchronized (mLock) {
if (mListener == null) {
- throw new IllegalStateException("Feature not ready.");
+ throw new RuntimeException("Feature not ready.");
}
try {
mListener.onSmsStatusReportReceived(token, messageRef, format, pdu);
@@ -268,4 +297,13 @@
public String getSmsFormat() {
return SmsMessage.FORMAT_3GPP;
}
+
+ /**
+ * Called when SmsImpl has been initialized and communication with the framework is set up.
+ * Any attempt by this class to access the framework before this method is called will return
+ * with an {@link RuntimeException}.
+ */
+ public void onReady() {
+ // Base Implementation - Should be overridden
+ }
}
diff --git a/telephony/java/com/android/ims/ImsReasonInfo.java b/telephony/java/com/android/ims/ImsReasonInfo.java
index 4f6f68c..83d9bd9 100644
--- a/telephony/java/com/android/ims/ImsReasonInfo.java
+++ b/telephony/java/com/android/ims/ImsReasonInfo.java
@@ -384,6 +384,13 @@
/** Call/IMS registration is failed/dropped because of a network detach */
public static final int CODE_NETWORK_DETACH = 1513;
+ /**
+ * Call failed due to SIP code 380 (Alternative Service response) while dialing an "undetected
+ * emergency number". This scenario is important in some regions where the carrier network will
+ * identify other non-emergency help numbers (e.g. mountain rescue) when attempting to dial.
+ */
+ public static final int CODE_SIP_ALTERNATE_EMERGENCY_CALL = 1514;
+
/* OEM specific error codes. To be used by OEMs when they don't want to
reveal error code which would be replaced by ERROR_UNSPECIFIED */
public static final int CODE_OEM_CAUSE_1 = 0xf001;
diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
index cce39f4..10c7f3e 100644
--- a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
+++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
@@ -61,4 +61,5 @@
oneway void acknowledgeSms(int token, int messageRef, int result);
oneway void acknowledgeSmsReport(int token, int messageRef, int result);
String getSmsFormat();
+ oneway void onSmsReady();
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index fba82ee..dfb3c34 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -47,6 +47,7 @@
import java.util.List;
+import android.telephony.UiccSlotInfo;
/**
* Interface used to interact with the phone. Mostly this is used by the
@@ -1444,4 +1445,19 @@
* @hide
*/
SignalStrength getSignalStrength(int subId);
+
+ /**
+ * Get slot info for all the UICC slots.
+ * @return UiccSlotInfo array.
+ * @hide
+ */
+ UiccSlotInfo[] getUiccSlotsInfo();
+
+ /**
+ * Map logicalSlot to physicalSlot, and activate the physicalSlot if it is inactive.
+ * @param physicalSlots Index i in the array representing physical slot for phone i. The array
+ * size should be same as getPhoneCount().
+ * @return boolean Return true if the switch succeeds, false if the switch fails.
+ */
+ boolean switchSlots(in int[] physicalSlots);
}
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index f804cb0..cdee9e6 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -105,6 +105,8 @@
int DEVICE_IN_USE = 64; /* Operation cannot be performed because the device
is currently in use */
int ABORTED = 65; /* Operation aborted */
+ int INVALID_RESPONSE = 66; /* Invalid response sent by vendor code */
+
// Below is list of OEM specific error codes which can by used by OEMs in case they don't want to
// reveal particular replacement for Generic failure
int OEM_ERROR_1 = 501;
@@ -419,6 +421,8 @@
int RIL_REQUEST_STOP_NETWORK_SCAN = 143;
int RIL_REQUEST_GET_SLOT_STATUS = 144;
int RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING = 145;
+ int RIL_REQUEST_START_KEEPALIVE = 146;
+ int RIL_REQUEST_STOP_KEEPALIVE = 147;
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -474,4 +478,5 @@
int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1048;
int RIL_UNSOL_NETWORK_SCAN_RESULT = 1049;
int RIL_UNSOL_ICC_SLOT_STATUS = 1050;
+ int RIL_UNSOL_KEEPALIVE_STATUS = 1051;
}
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index 9f8b3a8..c095438 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -837,6 +837,13 @@
}
/**
+ * Strip all the trailing 'F' characters of a string, e.g., an ICCID.
+ */
+ public static String stripTrailingFs(String s) {
+ return s == null ? null : s.replaceAll("(?i)f*$", "");
+ }
+
+ /**
* Converts a character of [0-9a-aA-F] to its hex value in a byte. If the character is not a
* hex number, 0 will be returned.
*/
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 2c60118..8d1a00b 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -20,6 +20,7 @@
import android.content.pm.PackageManager;
import android.net.IpConfiguration;
import android.net.IpConfiguration.ProxySettings;
+import android.net.MacAddress;
import android.net.ProxyInfo;
import android.net.StaticIpConfiguration;
import android.net.Uri;
@@ -54,8 +55,10 @@
/** {@hide} */
public static final String pskVarName = "psk";
/** {@hide} */
+ @Deprecated
public static final String[] wepKeyVarNames = { "wep_key0", "wep_key1", "wep_key2", "wep_key3" };
/** {@hide} */
+ @Deprecated
public static final String wepTxKeyIdxVarName = "wep_tx_keyidx";
/** {@hide} */
public static final String priorityVarName = "priority";
@@ -82,6 +85,9 @@
/** WPA is not used; plaintext or static WEP could be used. */
public static final int NONE = 0;
/** WPA pre-shared key (requires {@code preSharedKey} to be specified). */
+ /** @deprecated Due to security and performance limitations, use of WPA-1 networks
+ * is discouraged. WPA-2 (RSN) should be used instead. */
+ @Deprecated
public static final int WPA_PSK = 1;
/** WPA using EAP authentication. Generally used with an external authentication server. */
public static final int WPA_EAP = 2;
@@ -115,8 +121,8 @@
public static final String varName = "key_mgmt";
- public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP", "IEEE8021X",
- "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP" };
+ public static final String[] strings = { "NONE", /* deprecated */ "WPA_PSK", "WPA_EAP",
+ "IEEE8021X", "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP" };
}
/**
@@ -125,7 +131,10 @@
public static class Protocol {
private Protocol() { }
- /** WPA/IEEE 802.11i/D3.0 */
+ /** WPA/IEEE 802.11i/D3.0
+ * @deprecated Due to security and performance limitations, use of WPA-1 networks
+ * is discouraged. WPA-2 (RSN) should be used instead. */
+ @Deprecated
public static final int WPA = 0;
/** WPA2/IEEE 802.11i */
public static final int RSN = 1;
@@ -147,7 +156,10 @@
/** Open System authentication (required for WPA/WPA2) */
public static final int OPEN = 0;
- /** Shared Key authentication (requires static WEP keys) */
+ /** Shared Key authentication (requires static WEP keys)
+ * @deprecated Due to security and performance limitations, use of WEP networks
+ * is discouraged. */
+ @Deprecated
public static final int SHARED = 1;
/** LEAP/Network EAP (only used with LEAP) */
public static final int LEAP = 2;
@@ -165,7 +177,10 @@
/** Use only Group keys (deprecated) */
public static final int NONE = 0;
- /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] */
+ /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]
+ * @deprecated Due to security and performance limitations, use of WPA-1 networks
+ * is discouraged. WPA-2 (RSN) should be used instead. */
+ @Deprecated
public static final int TKIP = 1;
/** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */
public static final int CCMP = 2;
@@ -187,9 +202,15 @@
public static class GroupCipher {
private GroupCipher() { }
- /** WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11) */
+ /** WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11)
+ * @deprecated Due to security and performance limitations, use of WEP networks
+ * is discouraged. */
+ @Deprecated
public static final int WEP40 = 0;
- /** WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key */
+ /** WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key
+ * @deprecated Due to security and performance limitations, use of WEP networks
+ * is discouraged. */
+ @Deprecated
public static final int WEP104 = 1;
/** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] */
public static final int TKIP = 2;
@@ -203,7 +224,8 @@
public static final String varName = "group";
public static final String[] strings =
- { "WEP40", "WEP104", "TKIP", "CCMP", "GTK_NOT_USED" };
+ { /* deprecated */ "WEP40", /* deprecated */ "WEP104",
+ "TKIP", "CCMP", "GTK_NOT_USED" };
}
/** Possible status of a network configuration. */
@@ -309,10 +331,16 @@
* When the value of one of these keys is read, the actual key is
* not returned, just a "*" if the key has a value, or the null
* string otherwise.
+ * @deprecated Due to security and performance limitations, use of WEP networks
+ * is discouraged.
*/
+ @Deprecated
public String[] wepKeys;
- /** Default WEP key index, ranging from 0 to 3. */
+ /** Default WEP key index, ranging from 0 to 3.
+ * @deprecated Due to security and performance limitations, use of WEP networks
+ * is discouraged. */
+ @Deprecated
public int wepTxKeyIndex;
/**
@@ -852,6 +880,52 @@
@SystemApi
public int numAssociation;
+ /**
+ * @hide
+ * Randomized MAC address to use with this particular network
+ */
+ private MacAddress mRandomizedMacAddress;
+
+ /**
+ * @hide
+ * Checks if the given MAC address can be used for Connected Mac Randomization
+ * by verifying that it is non-null, unicast, and locally assigned.
+ * @param mac MacAddress to check
+ * @return true if mac is good to use
+ */
+ private boolean isValidMacAddressForRandomization(MacAddress mac) {
+ return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned();
+ }
+
+ /**
+ * @hide
+ * Returns Randomized MAC address to use with the network.
+ * If it is not set/valid, create a new randomized address.
+ */
+ public MacAddress getOrCreateRandomizedMacAddress() {
+ if (!isValidMacAddressForRandomization(mRandomizedMacAddress)) {
+ mRandomizedMacAddress = MacAddress.createRandomUnicastAddress();
+ }
+ return mRandomizedMacAddress;
+ }
+
+ /**
+ * @hide
+ * Returns MAC address set to be the local randomized MAC address.
+ * Does not guarantee that the returned address is valid for use.
+ */
+ public MacAddress getRandomizedMacAddress() {
+ return mRandomizedMacAddress;
+ }
+
+ /**
+ * @hide
+ * @param mac MacAddress to change into
+ */
+ public void setRandomizedMacAddress(MacAddress mac) {
+ mRandomizedMacAddress = mac;
+ }
+
/** @hide
* Boost given to RSSI on a home network for the purpose of calculating the score
* This adds stickiness to home networks, as defined by:
@@ -2124,6 +2198,7 @@
updateTime = source.updateTime;
shared = source.shared;
recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus());
+ mRandomizedMacAddress = source.mRandomizedMacAddress;
}
}
@@ -2191,6 +2266,7 @@
dest.writeInt(shared ? 1 : 0);
dest.writeString(mPasspointManagementObjectTree);
dest.writeInt(recentFailure.getAssociationStatus());
+ dest.writeParcelable(mRandomizedMacAddress, flags);
}
/** Implement the Parcelable interface {@hide} */
@@ -2259,6 +2335,7 @@
config.shared = in.readInt() != 0;
config.mPasspointManagementObjectTree = in.readString();
config.recentFailure.setAssociationStatus(in.readInt());
+ config.mRandomizedMacAddress = in.readParcelable(null);
return config;
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 50ae905..05dcb33 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -3593,26 +3593,6 @@
}
/**
- * Deprecated
- * Does nothing
- * @hide
- * @deprecated
- */
- public void setAllowScansWithTraffic(int enabled) {
- return;
- }
-
- /**
- * Deprecated
- * returns value for 'disabled'
- * @hide
- * @deprecated
- */
- public int getAllowScansWithTraffic() {
- return 0;
- }
-
- /**
* Resets all wifi manager settings back to factory defaults.
*
* @hide
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 622dce6..e7377c1 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertFalse;
import android.os.Parcel;
+import android.net.MacAddress;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import org.junit.Before;
@@ -49,6 +50,7 @@
String cookie = "C O.o |<IE";
WifiConfiguration config = new WifiConfiguration();
config.setPasspointManagementObjectTree(cookie);
+ MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress();
Parcel parcelW = Parcel.obtain();
config.writeToParcel(parcelW, 0);
byte[] bytes = parcelW.marshall();
@@ -59,8 +61,9 @@
parcelR.setDataPosition(0);
WifiConfiguration reconfig = WifiConfiguration.CREATOR.createFromParcel(parcelR);
- // lacking a useful config.equals, check one field near the end.
+ // lacking a useful config.equals, check two fields near the end.
assertEquals(cookie, reconfig.getMoTree());
+ assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress());
Parcel parcelWW = Parcel.obtain();
reconfig.writeToParcel(parcelWW, 0);
@@ -169,4 +172,48 @@
assertFalse(config.isOpenNetwork());
}
+
+ @Test
+ public void testGetOrCreateRandomizedMacAddress_SavesAndReturnsSameAddress() {
+ WifiConfiguration config = new WifiConfiguration();
+ MacAddress firstMacAddress = config.getOrCreateRandomizedMacAddress();
+ MacAddress secondMacAddress = config.getOrCreateRandomizedMacAddress();
+
+ assertEquals(firstMacAddress, secondMacAddress);
+ }
+
+ @Test
+ public void testSetRandomizedMacAddress_ChangesSavedAddress() {
+ WifiConfiguration config = new WifiConfiguration();
+ MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress();
+ config.setRandomizedMacAddress(macToChangeInto);
+ MacAddress macAfterChange = config.getOrCreateRandomizedMacAddress();
+
+ assertEquals(macToChangeInto, macAfterChange);
+ }
+
+ @Test
+ public void testGetOrCreateRandomizedMacAddress_ReRandomizesInvalidAddress() {
+ WifiConfiguration config = new WifiConfiguration();
+
+ MacAddress macAddressZeroes = MacAddress.ALL_ZEROS_ADDRESS;
+ MacAddress macAddressMulticast = MacAddress.fromString("03:ff:ff:ff:ff:ff");
+ MacAddress macAddressGlobal = MacAddress.fromString("fc:ff:ff:ff:ff:ff");
+
+ config.setRandomizedMacAddress(null);
+ MacAddress macAfterChange = config.getOrCreateRandomizedMacAddress();
+ assertFalse(macAfterChange.equals(null));
+
+ config.setRandomizedMacAddress(macAddressZeroes);
+ macAfterChange = config.getOrCreateRandomizedMacAddress();
+ assertFalse(macAfterChange.equals(macAddressZeroes));
+
+ config.setRandomizedMacAddress(macAddressMulticast);
+ macAfterChange = config.getOrCreateRandomizedMacAddress();
+ assertFalse(macAfterChange.equals(macAddressMulticast));
+
+ config.setRandomizedMacAddress(macAddressGlobal);
+ macAfterChange = config.getOrCreateRandomizedMacAddress();
+ assertFalse(macAfterChange.equals(macAddressGlobal));
+ }
}