Merge "Unhide Build.SUPPORTED_ABIS and friends."
diff --git a/Android.mk b/Android.mk
index 5d957d0..589279d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -130,6 +130,7 @@
core/java/android/content/pm/IPackageInstallObserver.aidl \
core/java/android/content/pm/IPackageInstallObserver2.aidl \
core/java/android/content/pm/IPackageInstaller.aidl \
+ core/java/android/content/pm/IPackageInstallerObserver.aidl \
core/java/android/content/pm/IPackageInstallerSession.aidl \
core/java/android/content/pm/IPackageManager.aidl \
core/java/android/content/pm/IPackageMoveObserver.aidl \
@@ -156,6 +157,9 @@
core/java/android/hardware/hdmi/IHdmiVendorCommandListener.aidl \
core/java/android/hardware/input/IInputManager.aidl \
core/java/android/hardware/input/IInputDevicesChangedListener.aidl \
+ core/java/android/hardware/location/IActivityRecognitionHardware.aidl \
+ core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl \
+ core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl \
core/java/android/hardware/location/IFusedLocationHardware.aidl \
core/java/android/hardware/location/IFusedLocationHardwareSink.aidl \
core/java/android/hardware/location/IGeofenceHardware.aidl \
@@ -288,6 +292,7 @@
location/java/android/location/IFusedProvider.aidl \
location/java/android/location/IGeocodeProvider.aidl \
location/java/android/location/IGeofenceProvider.aidl \
+ location/java/android/location/IGpsMeasurementsListener.aidl \
location/java/android/location/IGpsStatusListener.aidl \
location/java/android/location/IGpsStatusProvider.aidl \
location/java/android/location/ILocationListener.aidl \
@@ -342,11 +347,13 @@
telecomm/java/com/android/internal/telecomm/RemoteServiceCallback.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 \
telephony/java/com/android/ims/internal/IImsRegistrationListener.aidl \
telephony/java/com/android/ims/internal/IImsService.aidl \
telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl \
telephony/java/com/android/ims/internal/IImsUt.aidl \
telephony/java/com/android/ims/internal/IImsUtListener.aidl \
+ telephony/java/com/android/ims/ImsConfigListener.aidl \
telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \
telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \
telephony/java/com/android/internal/telephony/ITelephony.aidl \
diff --git a/api/current.txt b/api/current.txt
index 4846d32..f5cc80c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5146,6 +5146,11 @@
method public void onCommandResult(android.os.Bundle);
}
+ public static class VoiceInteractor.CompleteVoiceRequest extends android.app.VoiceInteractor.Request {
+ ctor public VoiceInteractor.CompleteVoiceRequest(java.lang.CharSequence, android.os.Bundle);
+ method public void onCompleteResult(android.os.Bundle);
+ }
+
public static class VoiceInteractor.ConfirmationRequest extends android.app.VoiceInteractor.Request {
ctor public VoiceInteractor.ConfirmationRequest(java.lang.CharSequence, android.os.Bundle);
method public void onConfirmationResult(boolean, android.os.Bundle);
@@ -5718,9 +5723,9 @@
method public android.bluetooth.BluetoothServerSocket listenUsingRfcommWithServiceRecord(java.lang.String, java.util.UUID) throws java.io.IOException;
method public boolean setName(java.lang.String);
method public boolean startDiscovery();
- method public boolean startLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
- method public boolean startLeScan(java.util.UUID[], android.bluetooth.BluetoothAdapter.LeScanCallback);
- method public void stopLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
+ method public deprecated boolean startLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
+ method public deprecated boolean startLeScan(java.util.UUID[], android.bluetooth.BluetoothAdapter.LeScanCallback);
+ method public deprecated void stopLeScan(android.bluetooth.BluetoothAdapter.LeScanCallback);
field public static final java.lang.String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
field public static final java.lang.String ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
field public static final java.lang.String ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";
@@ -6402,44 +6407,16 @@
public abstract class AdvertiseCallback {
ctor public AdvertiseCallback();
- method public abstract void onFailure(int);
- method public abstract void onSuccess(android.bluetooth.le.AdvertiseSettings);
+ method public void onStartFailure(int);
+ method public void onStartSuccess(android.bluetooth.le.AdvertiseSettings);
field public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3; // 0x3
- field public static final int ADVERTISE_FAILED_CONTROLLER_FAILURE = 5; // 0x5
- field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 7; // 0x7
- field public static final int ADVERTISE_FAILED_NOT_STARTED = 4; // 0x4
+ field public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5; // 0x5
+ field public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4; // 0x4
field public static final int ADVERTISE_FAILED_SERVICE_UNKNOWN = 1; // 0x1
field public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2; // 0x2
}
- public final class AdvertiseSettings implements android.os.Parcelable {
- method public int describeContents();
- method public int getMode();
- method public int getTxPowerLevel();
- method public int getType();
- method public void writeToParcel(android.os.Parcel, int);
- field public static final int ADVERTISE_MODE_BALANCED = 1; // 0x1
- field public static final int ADVERTISE_MODE_LOW_LATENCY = 2; // 0x2
- field public static final int ADVERTISE_MODE_LOW_POWER = 0; // 0x0
- field public static final int ADVERTISE_TX_POWER_HIGH = 3; // 0x3
- field public static final int ADVERTISE_TX_POWER_LOW = 1; // 0x1
- field public static final int ADVERTISE_TX_POWER_MEDIUM = 2; // 0x2
- field public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; // 0x0
- field public static final int ADVERTISE_TYPE_CONNECTABLE = 2; // 0x2
- field public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0; // 0x0
- field public static final int ADVERTISE_TYPE_SCANNABLE = 1; // 0x1
- field public static final android.os.Parcelable.Creator CREATOR;
- }
-
- public static final class AdvertiseSettings.Builder {
- ctor public AdvertiseSettings.Builder();
- method public android.bluetooth.le.AdvertiseSettings build();
- method public android.bluetooth.le.AdvertiseSettings.Builder setAdvertiseMode(int);
- method public android.bluetooth.le.AdvertiseSettings.Builder setTxPowerLevel(int);
- method public android.bluetooth.le.AdvertiseSettings.Builder setType(int);
- }
-
- public final class AdvertisementData implements android.os.Parcelable {
+ public final class AdvertiseData implements android.os.Parcelable {
method public int describeContents();
method public boolean getIncludeTxPowerLevel();
method public int getManufacturerId();
@@ -6451,43 +6428,69 @@
field public static final android.os.Parcelable.Creator CREATOR;
}
- public static final class AdvertisementData.Builder {
- ctor public AdvertisementData.Builder();
- method public android.bluetooth.le.AdvertisementData build();
- method public android.bluetooth.le.AdvertisementData.Builder setIncludeTxPowerLevel(boolean);
- method public android.bluetooth.le.AdvertisementData.Builder setManufacturerData(int, byte[]);
- method public android.bluetooth.le.AdvertisementData.Builder setServiceData(android.os.ParcelUuid, byte[]);
- method public android.bluetooth.le.AdvertisementData.Builder setServiceUuids(java.util.List<android.os.ParcelUuid>);
+ public static final class AdvertiseData.Builder {
+ ctor public AdvertiseData.Builder();
+ method public android.bluetooth.le.AdvertiseData build();
+ method public android.bluetooth.le.AdvertiseData.Builder setIncludeTxPowerLevel(boolean);
+ method public android.bluetooth.le.AdvertiseData.Builder setManufacturerData(int, byte[]);
+ method public android.bluetooth.le.AdvertiseData.Builder setServiceData(android.os.ParcelUuid, byte[]);
+ method public android.bluetooth.le.AdvertiseData.Builder setServiceUuids(java.util.List<android.os.ParcelUuid>);
+ }
+
+ public final class AdvertiseSettings implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean getIsConnectable();
+ method public int getMode();
+ method public int getTimeout();
+ method public int getTxPowerLevel();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final int ADVERTISE_MODE_BALANCED = 1; // 0x1
+ field public static final int ADVERTISE_MODE_LOW_LATENCY = 2; // 0x2
+ field public static final int ADVERTISE_MODE_LOW_POWER = 0; // 0x0
+ field public static final int ADVERTISE_TX_POWER_HIGH = 3; // 0x3
+ field public static final int ADVERTISE_TX_POWER_LOW = 1; // 0x1
+ field public static final int ADVERTISE_TX_POWER_MEDIUM = 2; // 0x2
+ field public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0; // 0x0
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public static final class AdvertiseSettings.Builder {
+ ctor public AdvertiseSettings.Builder();
+ method public android.bluetooth.le.AdvertiseSettings build();
+ method public android.bluetooth.le.AdvertiseSettings.Builder setAdvertiseMode(int);
+ method public android.bluetooth.le.AdvertiseSettings.Builder setIsConnectable(boolean);
+ method public android.bluetooth.le.AdvertiseSettings.Builder setTimeout(int);
+ method public android.bluetooth.le.AdvertiseSettings.Builder setTxPowerLevel(int);
}
public final class BluetoothLeAdvertiser {
- method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertisementData, android.bluetooth.le.AdvertiseCallback);
- method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertisementData, android.bluetooth.le.AdvertisementData, android.bluetooth.le.AdvertiseCallback);
+ method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
+ method public void startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback);
method public void stopAdvertising(android.bluetooth.le.AdvertiseCallback);
}
public final class BluetoothLeScanner {
+ method public void flushPendingScanResults(android.bluetooth.le.ScanCallback);
+ method public void startScan(android.bluetooth.le.ScanCallback);
method public void startScan(java.util.List<android.bluetooth.le.ScanFilter>, android.bluetooth.le.ScanSettings, android.bluetooth.le.ScanCallback);
method public void stopScan(android.bluetooth.le.ScanCallback);
}
public abstract class ScanCallback {
ctor public ScanCallback();
- method public abstract void onAdvertisementFound(android.bluetooth.le.ScanResult);
- method public abstract void onAdvertisementLost(android.bluetooth.le.ScanResult);
- method public abstract void onAdvertisementUpdate(android.bluetooth.le.ScanResult);
- method public abstract void onScanFailed(int);
+ method public void onBatchScanResults(java.util.List<android.bluetooth.le.ScanResult>);
+ method public void onScanFailed(int);
+ method public void onScanResult(int, android.bluetooth.le.ScanResult);
field public static final int SCAN_FAILED_ALREADY_STARTED = 1; // 0x1
field public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2; // 0x2
- field public static final int SCAN_FAILED_CONTROLLER_FAILURE = 4; // 0x4
- field public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 5; // 0x5
- field public static final int SCAN_FAILED_GATT_SERVICE_FAILURE = 3; // 0x3
+ field public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 4; // 0x4
+ field public static final int SCAN_FAILED_INTERNAL_ERROR = 3; // 0x3
}
public final class ScanFilter implements android.os.Parcelable {
method public int describeContents();
method public java.lang.String getDeviceAddress();
- method public java.lang.String getLocalName();
+ method public java.lang.String getDeviceName();
method public byte[] getManufacturerData();
method public byte[] getManufacturerDataMask();
method public int getManufacturerId();
@@ -6505,10 +6508,10 @@
public static final class ScanFilter.Builder {
ctor public ScanFilter.Builder();
method public android.bluetooth.le.ScanFilter build();
- method public android.bluetooth.le.ScanFilter.Builder setMacAddress(java.lang.String);
+ method public android.bluetooth.le.ScanFilter.Builder setDeviceAddress(java.lang.String);
+ method public android.bluetooth.le.ScanFilter.Builder setDeviceName(java.lang.String);
method public android.bluetooth.le.ScanFilter.Builder setManufacturerData(int, byte[]);
method public android.bluetooth.le.ScanFilter.Builder setManufacturerData(int, byte[], byte[]);
- method public android.bluetooth.le.ScanFilter.Builder setName(java.lang.String);
method public android.bluetooth.le.ScanFilter.Builder setRssiRange(int, int);
method public android.bluetooth.le.ScanFilter.Builder setServiceData(byte[]);
method public android.bluetooth.le.ScanFilter.Builder setServiceData(byte[], byte[]);
@@ -6518,7 +6521,7 @@
public final class ScanRecord {
method public int getAdvertiseFlags();
- method public java.lang.String getLocalName();
+ method public java.lang.String getDeviceName();
method public int getManufacturerId();
method public byte[] getManufacturerSpecificData();
method public byte[] getServiceData();
@@ -6541,13 +6544,13 @@
public final class ScanSettings implements android.os.Parcelable {
method public int describeContents();
method public int getCallbackType();
- method public long getReportDelayNanos();
+ method public long getReportDelaySeconds();
method public int getScanMode();
method public int getScanResultType();
method public void writeToParcel(android.os.Parcel, int);
- field public static final int CALLBACK_TYPE_ON_FOUND = 1; // 0x1
- field public static final int CALLBACK_TYPE_ON_LOST = 2; // 0x2
- field public static final int CALLBACK_TYPE_ON_UPDATE = 0; // 0x0
+ field public static final int CALLBACK_TYPE_ALL_MATCHES = 1; // 0x1
+ field public static final int CALLBACK_TYPE_FIRST_MATCH = 2; // 0x2
+ field public static final int CALLBACK_TYPE_MATCH_LOST = 4; // 0x4
field public static final android.os.Parcelable.Creator CREATOR;
field public static final int SCAN_MODE_BALANCED = 1; // 0x1
field public static final int SCAN_MODE_LOW_LATENCY = 2; // 0x2
@@ -6559,7 +6562,7 @@
ctor public ScanSettings.Builder();
method public android.bluetooth.le.ScanSettings build();
method public android.bluetooth.le.ScanSettings.Builder setCallbackType(int);
- method public android.bluetooth.le.ScanSettings.Builder setReportDelayNanos(long);
+ method public android.bluetooth.le.ScanSettings.Builder setReportDelaySeconds(long);
method public android.bluetooth.le.ScanSettings.Builder setScanMode(int);
}
@@ -7679,6 +7682,7 @@
field public static final int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 8388608; // 0x800000
field public static final int FLAG_ACTIVITY_FORWARD_RESULT = 33554432; // 0x2000000
field public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 1048576; // 0x100000
+ field public static final int FLAG_ACTIVITY_LAUNCH_BEHIND = 4096; // 0x1000
field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000
field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; // 0x80000
field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000
@@ -13324,7 +13328,6 @@
method public void setBackDisposition(int);
method public void setCandidatesView(android.view.View);
method public void setCandidatesViewShown(boolean);
- method public void setCursorAnchorMonitorMode(int);
method public void setExtractView(android.view.View);
method public void setExtractViewShown(boolean);
method public void setInputView(android.view.View);
@@ -13336,8 +13339,6 @@
field public static final int BACK_DISPOSITION_DEFAULT = 0; // 0x0
field public static final int BACK_DISPOSITION_WILL_DISMISS = 2; // 0x2
field public static final int BACK_DISPOSITION_WILL_NOT_DISMISS = 1; // 0x1
- field public static final int CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT = 1; // 0x1
- field public static final int CURSOR_ANCHOR_MONITOR_MODE_NONE = 0; // 0x0
}
public class InputMethodService.InputMethodImpl extends android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl {
@@ -14204,11 +14205,14 @@
public abstract class Image implements java.lang.AutoCloseable {
method public abstract void close();
+ method public android.graphics.Rect getCropRect();
method public abstract int getFormat();
method public abstract int getHeight();
method public abstract android.media.Image.Plane[] getPlanes();
method public abstract long getTimestamp();
method public abstract int getWidth();
+ method public void setCropRect(android.graphics.Rect);
+ field protected android.graphics.Rect mCropRect;
}
public static abstract class Image.Plane {
@@ -14283,15 +14287,22 @@
method public final int dequeueOutputBuffer(android.media.MediaCodec.BufferInfo, long);
method public final void flush();
method public android.media.MediaCodecInfo getCodecInfo();
- method public java.nio.ByteBuffer[] getInputBuffers();
+ method public java.nio.ByteBuffer getInputBuffer(int);
+ method public deprecated java.nio.ByteBuffer[] getInputBuffers();
+ method public final android.media.MediaFormat getInputFormat();
+ method public android.media.Image getInputImage(int);
method public final java.lang.String getName();
- method public java.nio.ByteBuffer[] getOutputBuffers();
+ method public java.nio.ByteBuffer getOutputBuffer(int);
+ method public deprecated java.nio.ByteBuffer[] getOutputBuffers();
method public final android.media.MediaFormat getOutputFormat();
+ method public final android.media.MediaFormat getOutputFormat(int);
+ method public android.media.Image getOutputImage(int);
method public final void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
method public final void queueSecureInputBuffer(int, int, android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException;
method public final void release();
method public final void releaseOutputBuffer(int, boolean);
method public final void releaseOutputBuffer(int, long);
+ method public void setCallback(android.media.MediaCodec.Callback);
method public final void setParameters(android.os.Bundle);
method public final void setVideoScalingMode(int);
method public final void signalEndOfInputStream();
@@ -14299,11 +14310,12 @@
method public final void stop();
field public static final int BUFFER_FLAG_CODEC_CONFIG = 2; // 0x2
field public static final int BUFFER_FLAG_END_OF_STREAM = 4; // 0x4
- field public static final int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
+ field public static final int BUFFER_FLAG_KEY_FRAME = 1; // 0x1
+ field public static final deprecated int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1
field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0
- field public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd
+ field public static final deprecated int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd
field public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; // 0xfffffffe
field public static final int INFO_TRY_AGAIN_LATER = -1; // 0xffffffff
field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
@@ -14322,6 +14334,14 @@
field public int size;
}
+ public static abstract class MediaCodec.Callback {
+ ctor public MediaCodec.Callback();
+ method public abstract void onError(android.media.MediaCodec, android.media.MediaCodec.CodecException);
+ method public abstract void onInputBufferAvailable(android.media.MediaCodec, int);
+ method public abstract void onOutputBufferAvailable(android.media.MediaCodec, int, android.media.MediaCodec.BufferInfo);
+ method public abstract void onOutputFormatChanged(android.media.MediaCodec, android.media.MediaFormat);
+ }
+
public static final class MediaCodec.CodecException extends java.lang.IllegalStateException {
ctor public MediaCodec.CodecException(int, int, java.lang.String);
method public int getErrorCode();
@@ -14809,6 +14829,7 @@
method public int getAudioSessionId();
method public int getCurrentPosition();
method public int getDuration();
+ method public int getSelectedTrack(int) throws java.lang.IllegalStateException;
method public android.media.MediaPlayer.TrackInfo[] getTrackInfo() throws java.lang.IllegalStateException;
method public int getVideoHeight();
method public int getVideoWidth();
@@ -14908,6 +14929,7 @@
method public int getTrackType();
method public void writeToParcel(android.os.Parcel, int);
field public static final int MEDIA_TRACK_TYPE_AUDIO = 2; // 0x2
+ field public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4; // 0x4
field public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3; // 0x3
field public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0; // 0x0
field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
@@ -16149,6 +16171,7 @@
method public boolean onKeyUp(int, android.view.KeyEvent);
method public abstract void onRelease();
method public boolean onSelectTrack(android.media.tv.TvTrackInfo);
+ method public abstract void onSetCaptionEnabled(boolean);
method public abstract void onSetStreamVolume(float);
method public abstract boolean onSetSurface(android.view.Surface);
method public boolean onTouchEvent(android.view.MotionEvent);
@@ -16198,6 +16221,7 @@
method public boolean onUnhandledInputEvent(android.view.InputEvent);
method public void reset();
method public void selectTrack(android.media.tv.TvTrackInfo);
+ method public void setCaptionEnabled(boolean);
method public void setOnUnhandledInputEventListener(android.media.tv.TvView.OnUnhandledInputEventListener);
method public void setStreamVolume(float);
method public void setTvInputListener(android.media.tv.TvView.TvInputListener);
@@ -17331,6 +17355,23 @@
enum_constant public static final android.net.wifi.SupplicantState UNINITIALIZED;
}
+ public class WifiAdapter implements android.os.Parcelable {
+ method public int describeContents();
+ method public java.lang.String getName();
+ method public boolean is5GHzBandSupported();
+ method public boolean isDeviceToApRttSupported();
+ method public boolean isDeviceToDeviceRttSupported();
+ method public boolean isEnhancedPowerReportingSupported();
+ method public boolean isOffChannelTdlsSupported();
+ method public boolean isP2pSupported();
+ method public boolean isPasspointSupported();
+ method public boolean isPortableHotspotSupported();
+ method public boolean isPreferredNetworkOffloadSupported();
+ method public boolean isTdlsSupported();
+ method public boolean isWifiScannerSupported();
+ method public void writeToParcel(android.os.Parcel, int);
+ }
+
public class WifiConfiguration implements android.os.Parcelable {
ctor public WifiConfiguration();
method public int describeContents();
@@ -17469,6 +17510,7 @@
method public boolean disableNetwork(int);
method public boolean disconnect();
method public boolean enableNetwork(int, boolean);
+ method public java.util.List<android.net.wifi.WifiAdapter> getAdapters();
method public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
method public android.net.wifi.WifiInfo getConnectionInfo();
method public android.net.DhcpInfo getDhcpInfo();
@@ -26518,6 +26560,7 @@
method public abstract void onCancel(android.service.voice.VoiceInteractionSession.Request);
method public void onCloseSystemDialogs();
method public abstract void onCommand(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.String, android.os.Bundle);
+ method public void onCompleteVoice(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle);
method public void onComputeInsets(android.service.voice.VoiceInteractionSession.Insets);
method public abstract void onConfirm(android.service.voice.VoiceInteractionSession.Caller, android.service.voice.VoiceInteractionSession.Request, java.lang.CharSequence, android.os.Bundle);
method public void onCreate(android.os.Bundle);
@@ -26553,6 +26596,7 @@
method public void sendAbortVoiceResult(android.os.Bundle);
method public void sendCancelResult();
method public void sendCommandResult(boolean, android.os.Bundle);
+ method public void sendCompleteVoiceResult(android.os.Bundle);
method public void sendConfirmResult(boolean, android.os.Bundle);
}
@@ -27546,7 +27590,7 @@
public final class CallCapabilities {
method public static java.lang.String toString(int);
field public static final int ADD_CALL = 16; // 0x10
- field public static final int ALL = 255; // 0xff
+ field public static final int ALL = 3327; // 0xcff
field public static final int GENERIC_CONFERENCE = 128; // 0x80
field public static final int HOLD = 1; // 0x1
field public static final int MERGE_CALLS = 4; // 0x4
@@ -27556,21 +27600,16 @@
field public static final int SUPPORTS_VT_REMOTE = 512; // 0x200
field public static final int SUPPORT_HOLD = 2; // 0x2
field public static final int SWAP_CALLS = 8; // 0x8
+ field public static final int VoLTE = 1024; // 0x400
+ field public static final int VoWIFI = 2048; // 0x800
}
- public final class CallFeatures {
- field public static final int NONE = 0; // 0x0
- field public static final int VoLTE = 1; // 0x1
- field public static final int VoWIFI = 2; // 0x2
- }
-
- public final class CallNumberPresentation extends java.lang.Enum {
- method public static android.telecomm.CallNumberPresentation valueOf(java.lang.String);
- method public static final android.telecomm.CallNumberPresentation[] values();
- enum_constant public static final android.telecomm.CallNumberPresentation ALLOWED;
- enum_constant public static final android.telecomm.CallNumberPresentation PAYPHONE;
- enum_constant public static final android.telecomm.CallNumberPresentation RESTRICTED;
- enum_constant public static final android.telecomm.CallNumberPresentation UNKNOWN;
+ public class CallPropertyPresentation {
+ ctor public CallPropertyPresentation();
+ field public static final int ALLOWED = 0; // 0x0
+ field public static final int PAYPHONE = 3; // 0x3
+ field public static final int RESTRICTED = 1; // 0x1
+ field public static final int UNKNOWN = 2; // 0x2
}
public final class CallServiceDescriptor implements android.os.Parcelable {
@@ -27653,9 +27692,11 @@
method public final android.telecomm.CallAudioState getCallAudioState();
method public final int getCallCapabilities();
method public final android.telecomm.CallVideoProvider getCallVideoProvider();
+ method public final java.lang.String getCallerDisplayName();
+ method public final int getCallerDisplayNamePresentation();
method public final java.util.List<android.telecomm.Connection> getChildConnections();
- method public final int getFeatures();
method public final android.net.Uri getHandle();
+ method public final int getHandlePresentation();
method public final android.telecomm.Connection getParentConnection();
method public final int getState();
method public final android.telecomm.StatusHints getStatusHints();
@@ -27674,16 +27715,17 @@
method protected void onSetAudioState(android.telecomm.CallAudioState);
method protected void onSetState(int);
method protected void onStopDtmfTone();
+ method protected void onSwapWithBackgroundCall();
method protected void onUnhold();
method public final void setActive();
method public final void setAudioModeIsVoip(boolean);
method public final void setCallCapabilities(int);
method public final void setCallVideoProvider(android.telecomm.CallVideoProvider);
+ method public final void setCallerDisplayName(java.lang.String, int);
method public final void setDestroyed();
method public final void setDialing();
method public final void setDisconnected(int, java.lang.String);
- method public final void setFeatures(int);
- method public final void setHandle(android.net.Uri);
+ method public final void setHandle(android.net.Uri, int);
method public final void setOnHold();
method public final void setParentConnection(android.telecomm.Connection);
method public final void setPostDialWait(java.lang.String);
@@ -27704,13 +27746,13 @@
}
public final class ConnectionRequest implements android.os.Parcelable {
- ctor public ConnectionRequest(java.lang.String, android.net.Uri, android.os.Bundle, int);
- ctor public ConnectionRequest(android.telecomm.PhoneAccount, java.lang.String, android.net.Uri, android.os.Bundle, int);
+ ctor public ConnectionRequest(android.telecomm.PhoneAccount, java.lang.String, android.net.Uri, int, android.os.Bundle, int);
method public int describeContents();
method public android.telecomm.PhoneAccount getAccount();
method public java.lang.String getCallId();
method public android.os.Bundle getExtras();
method public android.net.Uri getHandle();
+ method public int getHandlePresentation();
method public int getVideoState();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
@@ -27757,6 +27799,7 @@
method public void rejectCall(java.lang.String, boolean, java.lang.String);
method public void setAudioRoute(int);
method public void stopDtmfTone(java.lang.String);
+ method public void swapWithBackgroundCall(java.lang.String);
method public void unholdCall(java.lang.String);
}
@@ -27764,15 +27807,17 @@
method public int describeContents();
method public android.telecomm.PhoneAccount getAccount();
method public android.telecomm.RemoteCallVideoProvider getCallVideoProvider() throws android.os.RemoteException;
+ method public java.lang.String getCallerDisplayName();
+ method public int getCallerDisplayNamePresentation();
method public java.util.List<java.lang.String> getCannedSmsResponses();
method public int getCapabilities();
method public long getConnectTimeMillis();
method public android.telecomm.CallServiceDescriptor getCurrentCallServiceDescriptor();
method public int getDisconnectCauseCode();
method public java.lang.String getDisconnectCauseMsg();
- method public int getFeatures();
method public android.telecomm.GatewayInfo getGatewayInfo();
method public android.net.Uri getHandle();
+ method public int getHandlePresentation();
method public java.lang.String getId();
method public android.telecomm.CallState getState();
method public android.telecomm.StatusHints getStatusHints();
@@ -27841,9 +27886,12 @@
method public void disconnect();
method public boolean getAudioModeIsVoip();
method public int getCallCapabilities();
+ method public java.lang.String getCallerDisplayName();
+ method public int getCallerDisplayNamePresentation();
method public int getDisconnectCause();
method public java.lang.String getDisconnectMessage();
- method public int getFeatures();
+ method public android.net.Uri getHandle();
+ method public int getHandlePresentation();
method public int getState();
method public android.telecomm.StatusHints getStatusHints();
method public void hold();
@@ -27853,19 +27901,21 @@
method public void removeListener(android.telecomm.RemoteConnection.Listener);
method public void setAudioState(android.telecomm.CallAudioState);
method public void stopDtmf();
+ method public void swapWithBackgroundCall();
method public void unhold();
}
public static abstract interface RemoteConnection.Listener {
+ method public abstract void onAudioModeIsVoipChanged(android.telecomm.RemoteConnection, boolean);
method public abstract void onCallCapabilitiesChanged(android.telecomm.RemoteConnection, int);
+ method public abstract void onCallerDisplayNameChanged(android.telecomm.RemoteConnection, java.lang.String, int);
method public abstract void onDestroyed(android.telecomm.RemoteConnection);
method public abstract void onDisconnected(android.telecomm.RemoteConnection, int, java.lang.String);
- method public abstract void onFeaturesChanged(android.telecomm.RemoteConnection, int);
+ method public abstract void onHandleChanged(android.telecomm.RemoteConnection, android.net.Uri, int);
method public abstract void onPostDialWait(android.telecomm.RemoteConnection, java.lang.String);
method public abstract void onRequestingRingback(android.telecomm.RemoteConnection, boolean);
- method public abstract void onSetAudioModeIsVoip(android.telecomm.RemoteConnection, boolean);
- method public abstract void onSetStatusHints(android.telecomm.RemoteConnection, android.telecomm.StatusHints);
method public abstract void onStateChanged(android.telecomm.RemoteConnection, int);
+ method public abstract void onStatusHintsChanged(android.telecomm.RemoteConnection, android.telecomm.StatusHints);
}
public abstract interface Response {
@@ -27879,9 +27929,10 @@
}
public final class StatusHints implements android.os.Parcelable {
- ctor public StatusHints(android.content.ComponentName, java.lang.String, int);
+ ctor public StatusHints(android.content.ComponentName, java.lang.String, int, android.os.Bundle);
method public int describeContents();
method public android.content.ComponentName getComponentName();
+ method public android.os.Bundle getExtras();
method public android.graphics.drawable.Drawable getIcon(android.content.Context);
method public int getIconId();
method public java.lang.String getLabel();
@@ -31288,10 +31339,17 @@
public final class Range {
ctor public Range(T, T);
+ method public T clamp(T);
+ method public boolean contains(T);
+ method public boolean contains(android.util.Range<T>);
method public static android.util.Range<T> create(T, T);
+ method public android.util.Range<T> extend(android.util.Range<T>);
+ method public android.util.Range<T> extend(T, T);
+ method public android.util.Range<T> extend(T);
method public T getLower();
method public T getUpper();
- method public boolean inRange(T);
+ method public android.util.Range<T> intersect(android.util.Range<T>);
+ method public android.util.Range<T> intersect(T, T);
}
public final class Rational extends java.lang.Number implements java.lang.Comparable {
@@ -31307,6 +31365,7 @@
method public boolean isNaN();
method public boolean isZero();
method public long longValue();
+ method public static android.util.Rational parseRational(java.lang.String) throws java.lang.NumberFormatException;
field public static final android.util.Rational NEGATIVE_INFINITY;
field public static final android.util.Rational NaN;
field public static final android.util.Rational POSITIVE_INFINITY;
@@ -31317,6 +31376,7 @@
ctor public Size(int, int);
method public int getHeight();
method public int getWidth();
+ method public static android.util.Size parseSize(java.lang.String) throws java.lang.NumberFormatException;
}
public final class SizeF {
@@ -33612,6 +33672,7 @@
method protected boolean getChildStaticTransformation(android.view.View, android.view.animation.Transformation);
method public boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
method public boolean getClipChildren();
+ method public boolean getClipToPadding();
method public int getDescendantFocusability();
method public android.view.View getFocusedChild();
method public android.view.animation.LayoutAnimationController getLayoutAnimation();
@@ -34979,6 +35040,7 @@
method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
method public static final void removeComposingSpans(android.text.Spannable);
method public boolean reportFullscreenMode(boolean);
+ method public int requestCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfoRequest);
method public boolean sendKeyEvent(android.view.KeyEvent);
method public boolean setComposingRegion(int, int);
method public static void setComposingSpans(android.text.Spannable);
@@ -35044,6 +35106,25 @@
method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int);
}
+ public final class CursorAnchorInfoRequest implements android.os.Parcelable {
+ ctor public CursorAnchorInfoRequest(int, int);
+ ctor public CursorAnchorInfoRequest(android.os.Parcel);
+ method public int describeContents();
+ method public int getRequestFlags();
+ method public int getRequestType();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ field public static final int FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE = 2; // 0x2
+ field public static final int FLAG_CURSOR_ANCHOR_INFO_MONITOR = 1; // 0x1
+ field public static final int FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES = 2; // 0x2
+ field public static final int FLAG_CURSOR_RECT_MONITOR = 1; // 0x1
+ field public static final int FLAG_CURSOR_RECT_WITH_VIEW_MATRIX = 4; // 0x4
+ field public static final int RESULT_NOT_HANDLED = 0; // 0x0
+ field public static final int RESULT_SCHEDULED = 1; // 0x1
+ field public static final int TYPE_CURSOR_ANCHOR_INFO = 1; // 0x1
+ field public static final int TYPE_CURSOR_RECT = 2; // 0x2
+ }
+
public class EditorInfo implements android.text.InputType android.os.Parcelable {
ctor public EditorInfo();
method public int describeContents();
@@ -35141,6 +35222,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 abstract int requestCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfoRequest);
method public abstract boolean sendKeyEvent(android.view.KeyEvent);
method public abstract boolean setComposingRegion(int, int);
method public abstract boolean setComposingText(java.lang.CharSequence, int);
@@ -35168,6 +35250,7 @@
method public boolean performEditorAction(int);
method public boolean performPrivateCommand(java.lang.String, android.os.Bundle);
method public boolean reportFullscreenMode(boolean);
+ method public int requestCursorAnchorInfo(android.view.inputmethod.CursorAnchorInfoRequest);
method public boolean sendKeyEvent(android.view.KeyEvent);
method public boolean setComposingRegion(int, int);
method public boolean setComposingText(java.lang.CharSequence, int);
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 92e9290..3d0eec4 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -27,10 +27,11 @@
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageManager;
+import android.content.pm.InstallSessionParams;
import android.content.pm.InstrumentationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.PackageInstaller.CommitResultCallback;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
@@ -768,6 +769,31 @@
}
}
+ class LocalCommitResultCallback extends CommitResultCallback {
+ boolean finished;
+ boolean success;
+ String msg;
+
+ private void setResult(boolean success, String msg) {
+ synchronized (this) {
+ this.finished = true;
+ this.success = success;
+ this.msg = msg;
+ notifyAll();
+ }
+ }
+
+ @Override
+ public void onSuccess() {
+ setResult(true, null);
+ }
+
+ @Override
+ public void onFailure(String msg) {
+ setResult(false, msg);
+ }
+ }
+
/**
* Converts a failure code into a string by using reflection to find a matching constant
* in PackageManager.
@@ -989,9 +1015,10 @@
private void runInstallCreate() throws RemoteException {
String installerPackageName = null;
- final PackageInstallerParams params = new PackageInstallerParams();
+ final InstallSessionParams params = new InstallSessionParams();
params.installFlags = PackageManager.INSTALL_ALL_USERS;
params.fullInstall = true;
+ params.progressMax = -1;
String opt;
while ((opt = nextOption()) != null) {
@@ -1016,6 +1043,7 @@
params.fullInstall = false;
} else if (opt.equals("-S")) {
params.deltaSize = Long.parseLong(nextOptionData());
+ params.progressMax = (int) params.deltaSize;
} else {
throw new IllegalArgumentException("Unknown option " + opt);
}
@@ -1067,7 +1095,8 @@
out = session.openWrite(splitName, 0, sizeBytes);
final int n = Streams.copy(in, out);
- out.flush();
+ session.fsync(out);
+ session.addProgress(n);
System.out.println("Success: streamed " + n + " bytes");
} finally {
@@ -1084,21 +1113,21 @@
try {
session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
- final LocalPackageInstallObserver observer = new LocalPackageInstallObserver();
- session.install(observer);
+ final LocalCommitResultCallback callback = new LocalCommitResultCallback();
+ session.commit(callback);
- synchronized (observer) {
- while (!observer.finished) {
+ synchronized (callback) {
+ while (!callback.finished) {
try {
- observer.wait();
+ callback.wait();
} catch (InterruptedException e) {
}
}
- if (observer.result != PackageManager.INSTALL_SUCCEEDED) {
- throw new IllegalStateException(
- "Failure [" + installFailureToString(observer) + "]");
+ if (!callback.success) {
+ throw new IllegalStateException("Failure [" + callback.msg + "]");
}
}
+
System.out.println("Success");
} finally {
IoUtils.closeQuietly(session);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3cf8f9a..e1d0b86 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -696,6 +696,12 @@
*/
public TaskDescription taskDescription;
+ /**
+ * Task affiliation for grouping with other tasks.
+ * @hide
+ */
+ public int affiliatedTaskId;
+
public RecentTaskInfo() {
}
@@ -727,6 +733,7 @@
dest.writeInt(userId);
dest.writeLong(firstActiveTime);
dest.writeLong(lastActiveTime);
+ dest.writeInt(affiliatedTaskId);
}
public void readFromParcel(Parcel source) {
@@ -741,6 +748,7 @@
userId = source.readInt();
firstActiveTime = source.readLong();
lastActiveTime = source.readLong();
+ affiliatedTaskId = source.readInt();
}
public static final Creator<RecentTaskInfo> CREATOR
diff --git a/core/java/android/app/PackageInstallObserver.java b/core/java/android/app/PackageInstallObserver.java
index 941efbd..7117111 100644
--- a/core/java/android/app/PackageInstallObserver.java
+++ b/core/java/android/app/PackageInstallObserver.java
@@ -23,13 +23,14 @@
public class PackageInstallObserver {
private final IPackageInstallObserver2.Stub mBinder = new IPackageInstallObserver2.Stub() {
@Override
- public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+ String msg) {
PackageInstallObserver.this.packageInstalled(basePackageName, extras, returnCode);
}
};
/** {@hide} */
- public IPackageInstallObserver2.Stub getBinder() {
+ public IPackageInstallObserver2 getBinder() {
return mBinder;
}
@@ -50,4 +51,9 @@
*/
public void packageInstalled(String basePackageName, Bundle extras, int returnCode) {
}
+
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+ String msg) {
+ packageInstalled(basePackageName, extras, returnCode);
+ }
}
diff --git a/core/java/android/app/PackageUninstallObserver.java b/core/java/android/app/PackageUninstallObserver.java
index 0a960a7..83fc380 100644
--- a/core/java/android/app/PackageUninstallObserver.java
+++ b/core/java/android/app/PackageUninstallObserver.java
@@ -28,7 +28,7 @@
};
/** {@hide} */
- public IPackageDeleteObserver.Stub getBinder() {
+ public IPackageDeleteObserver getBinder() {
return mBinder;
}
diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java
index abd042b..ac74ca1 100644
--- a/core/java/android/app/TimePickerDialog.java
+++ b/core/java/android/app/TimePickerDialog.java
@@ -16,11 +16,9 @@
package android.app;
-import android.app.UiModeManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
-import android.content.res.Configuration;
import android.os.Bundle;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -30,6 +28,7 @@
import com.android.internal.R;
+import static android.os.Build.VERSION_CODES.L;
/**
* A dialog that prompts the user for the time of day using a {@link TimePicker}.
@@ -108,18 +107,19 @@
Context themeContext = getContext();
+ final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+ if (targetSdkVersion < L) {
+ setIcon(0);
+ setTitle(R.string.time_picker_dialog_title);
+ setButton(BUTTON_POSITIVE, themeContext.getText(R.string.date_time_done), this);
+ }
+
LayoutInflater inflater =
(LayoutInflater) themeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.time_picker_dialog, null);
setView(view);
mTimePicker = (TimePicker) view.findViewById(R.id.timePicker);
- // Initialize state
- UiModeManager uiModeManager =
- (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
- if (uiModeManager.getCurrentModeType() != Configuration.UI_MODE_TYPE_TELEVISION) {
- mTimePicker.setLegacyMode(false /* will show new UI */);
- }
mTimePicker.setShowDoneButton(true);
mTimePicker.setDismissCallback(new TimePicker.TimePickerDismissCallback() {
@Override
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 64e3484..4aec9e0 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -673,6 +673,7 @@
canvas.translate(- screenshotWidth / 2, - screenshotHeight / 2);
canvas.drawBitmap(screenShot, 0, 0, null);
canvas.setBitmap(null);
+ screenShot.recycle();
screenShot = unrotatedScreenShot;
}
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 0d947217..dcdfd78 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -81,6 +81,16 @@
request.clear();
}
break;
+ case MSG_COMPLETE_VOICE_RESULT:
+ request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
+ if (DEBUG) Log.d(TAG, "onCompleteVoice: req="
+ + ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
+ + " result=" + args.arg1);
+ if (request != null) {
+ ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg2);
+ request.clear();
+ }
+ break;
case MSG_ABORT_VOICE_RESULT:
request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
if (DEBUG) Log.d(TAG, "onAbortVoice: req="
@@ -125,6 +135,12 @@
}
@Override
+ public void deliverCompleteVoiceResult(IVoiceInteractorRequest request, Bundle result) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
+ MSG_COMPLETE_VOICE_RESULT, request, result));
+ }
+
+ @Override
public void deliverAbortVoiceResult(IVoiceInteractorRequest request, Bundle result) {
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOO(
MSG_ABORT_VOICE_RESULT, request, result));
@@ -147,9 +163,10 @@
final ArrayMap<IBinder, Request> mActiveRequests = new ArrayMap<IBinder, Request>();
static final int MSG_CONFIRMATION_RESULT = 1;
- static final int MSG_ABORT_VOICE_RESULT = 2;
- static final int MSG_COMMAND_RESULT = 3;
- static final int MSG_CANCEL_RESULT = 4;
+ static final int MSG_COMPLETE_VOICE_RESULT = 2;
+ static final int MSG_ABORT_VOICE_RESULT = 3;
+ static final int MSG_COMMAND_RESULT = 4;
+ static final int MSG_CANCEL_RESULT = 5;
public static abstract class Request {
IVoiceInteractorRequest mRequestInterface;
@@ -228,6 +245,36 @@
}
}
+ public static class CompleteVoiceRequest extends Request {
+ final CharSequence mMessage;
+ final Bundle mExtras;
+
+ /**
+ * Reports that the current interaction was successfully completed with voice, so the
+ * application can report the final status to the user. When the response comes back, the
+ * voice system has handled the request and is ready to switch; at that point the
+ * application can start a new non-voice activity or finish. Be sure when starting the new
+ * activity to use {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK
+ * Intent.FLAG_ACTIVITY_NEW_TASK} to keep the new activity out of the current voice
+ * interaction task.
+ *
+ * @param message Optional message to tell user about the completion status of the task.
+ * @param extras Additional optional information.
+ */
+ public CompleteVoiceRequest(CharSequence message, Bundle extras) {
+ mMessage = message;
+ mExtras = extras;
+ }
+
+ public void onCompleteResult(Bundle result) {
+ }
+
+ IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
+ IVoiceInteractorCallback callback) throws RemoteException {
+ return interactor.startCompleteVoice(packageName, callback, mMessage, mExtras);
+ }
+ }
+
public static class AbortVoiceRequest extends Request {
final CharSequence mMessage;
final Bundle mExtras;
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index be14504..97e3fc5 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -20,7 +20,9 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.os.Handler;
import android.os.IBinder;
@@ -1734,7 +1736,10 @@
*
* @param callback the callback LE scan results are delivered
* @return true, if the scan was started successfully
+ * @deprecated use {@link BluetoothLeScanner#startScan(List, ScanSettings, ScanCallback)}
+ * instead.
*/
+ @Deprecated
public boolean startLeScan(LeScanCallback callback) {
return startLeScan(null, callback);
}
@@ -1751,7 +1756,10 @@
* @param serviceUuids Array of services to look for
* @param callback the callback LE scan results are delivered
* @return true, if the scan was started successfully
+ * @deprecated use {@link BluetoothLeScanner#startScan(List, ScanSettings, ScanCallback)}
+ * instead.
*/
+ @Deprecated
public boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) {
if (DBG) Log.d(TAG, "startLeScan(): " + serviceUuids);
@@ -1794,7 +1802,9 @@
*
* @param callback used to identify which scan to stop
* must be the same handle used to start the scan
+ * @deprecated Use {@link BluetoothLeScanner#stopScan(ScanCallback)} instead.
*/
+ @Deprecated
public void stopLeScan(LeScanCallback callback) {
if (DBG) Log.d(TAG, "stopLeScan()");
GattCallbackWrapper wrapper;
diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl
index 5ed10a6..0f0eee6 100644
--- a/core/java/android/bluetooth/IBluetoothGatt.aidl
+++ b/core/java/android/bluetooth/IBluetoothGatt.aidl
@@ -18,7 +18,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.AdvertiseSettings;
-import android.bluetooth.le.AdvertisementData;
+import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
import android.os.ParcelUuid;
@@ -40,8 +40,8 @@
void stopScan(in int appIf, in boolean isServer);
void flushPendingBatchResults(in int appIf, in boolean isServer);
void startMultiAdvertising(in int appIf,
- in AdvertisementData advertiseData,
- in AdvertisementData scanResponse,
+ in AdvertiseData advertiseData,
+ in AdvertiseData scanResponse,
in AdvertiseSettings settings);
void stopMultiAdvertising(in int appIf);
void registerClient(in ParcelUuid appId, in IBluetoothGattCallback callback);
diff --git a/core/java/android/bluetooth/le/AdvertiseCallback.java b/core/java/android/bluetooth/le/AdvertiseCallback.java
index 1d26ef9..2afbadf4 100644
--- a/core/java/android/bluetooth/le/AdvertiseCallback.java
+++ b/core/java/android/bluetooth/le/AdvertiseCallback.java
@@ -17,64 +17,58 @@
package android.bluetooth.le;
/**
- * Callback of Bluetooth LE advertising, which is used to deliver advertising operation status.
+ * Bluetooth LE advertising callbacks, used to deliver advertising operation status.
*/
public abstract class AdvertiseCallback {
/**
- * The operation is success.
- *
+ * The requested operation was successful.
* @hide
*/
- public static final int SUCCESS = 0;
+ public static final int ADVERTISE_SUCCESS = 0;
+
/**
- * Fails to start advertising as the advertisement data contains services that are not added to
- * the local bluetooth GATT server.
+ * Failed to start advertising as the advertisement data contains services that are not
+ * added to the local Bluetooth GATT server.
*/
public static final int ADVERTISE_FAILED_SERVICE_UNKNOWN = 1;
+
/**
- * Fails to start advertising as system runs out of quota for advertisers.
+ * Failed to start advertising because no advertising instance is available.
*/
public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2;
/**
- * Fails to start advertising as the advertising is already started.
+ * Failed to start advertising as the advertising is already started.
*/
public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3;
+
/**
- * Fails to stop advertising as the advertising is not started.
+ * Operation failed due to an internal error.
*/
- public static final int ADVERTISE_FAILED_NOT_STARTED = 4;
+ public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4;
/**
- * Operation fails due to bluetooth controller failure.
+ * This feature is not supported on this platform.
*/
- public static final int ADVERTISE_FAILED_CONTROLLER_FAILURE = 5;
-
- /**
- * Operation fails due to GATT service failure.
- * @hide
- */
- public static final int ADVERTISE_FAILED_GATT_SERVICE_FAILURE = 6;
-
- /**
- * Operation fails as this feature is not supported
- */
- public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 7;
+ public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5;
/**
- * Callback when advertising operation succeeds.
+ * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertising} indicating
+ * that the advertising has been started successfully.
*
* @param settingsInEffect The actual settings used for advertising, which may be different from
- * what the app asks.
+ * what has been requested.
*/
- public abstract void onSuccess(AdvertiseSettings settingsInEffect);
+ public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+ }
/**
- * Callback when advertising operation fails.
+ * Callback when advertising could not be started.
*
- * @param errorCode Error code for failures.
+ * @param errorCode Error code (see ADVERTISE_FAILED_* constants) for
*/
- public abstract void onFailure(int errorCode);
+ public void onStartFailure(int errorCode) {
+ }
}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/bluetooth/le/AdvertiseData.aidl
similarity index 95%
rename from core/java/android/bluetooth/le/AdvertisementData.aidl
rename to core/java/android/bluetooth/le/AdvertiseData.aidl
index 3da1321..bcbf224 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/bluetooth/le/AdvertiseData.aidl
@@ -16,4 +16,4 @@
package android.bluetooth.le;
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable AdvertiseData;
diff --git a/core/java/android/bluetooth/le/AdvertisementData.java b/core/java/android/bluetooth/le/AdvertiseData.java
similarity index 83%
rename from core/java/android/bluetooth/le/AdvertisementData.java
rename to core/java/android/bluetooth/le/AdvertiseData.java
index c587204..d0f52b2 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.java
+++ b/core/java/android/bluetooth/le/AdvertiseData.java
@@ -27,16 +27,16 @@
import java.util.List;
/**
- * Advertisement data packet for Bluetooth LE advertising. This represents the data to be
- * broadcasted in Bluetooth LE advertising as well as the scan response for active scan.
- * <p>
- * Use {@link AdvertisementData.Builder} to create an instance of {@link AdvertisementData} to be
+ * Advertisement data packet container for Bluetooth LE advertising. This represents the data to be
+ * advertised as well as the scan response data for active scans.
+ *
+ * <p>Use {@link AdvertiseData.Builder} to create an instance of {@link AdvertiseData} to be
* advertised.
*
* @see BluetoothLeAdvertiser
* @see ScanRecord
*/
-public final class AdvertisementData implements Parcelable {
+public final class AdvertiseData implements Parcelable {
@Nullable
private final List<ParcelUuid> mServiceUuids;
@@ -52,7 +52,7 @@
private boolean mIncludeTxPowerLevel;
- private AdvertisementData(List<ParcelUuid> serviceUuids,
+ private AdvertiseData(List<ParcelUuid> serviceUuids,
ParcelUuid serviceDataUuid, byte[] serviceData,
int manufacturerId,
byte[] manufacturerSpecificData, boolean includeTxPowerLevel) {
@@ -65,8 +65,8 @@
}
/**
- * Returns a list of service uuids within the advertisement that are used to identify the
- * bluetooth GATT services.
+ * Returns a list of service UUIDs within the advertisement that are used to identify the
+ * Bluetooth GATT services.
*/
public List<ParcelUuid> getServiceUuids() {
return mServiceUuids;
@@ -89,15 +89,14 @@
}
/**
- * Returns a 16 bit uuid of the service that the service data is associated with.
+ * Returns a 16-bit UUID of the service that the service data is associated with.
*/
public ParcelUuid getServiceDataUuid() {
return mServiceDataUuid;
}
/**
- * Returns service data. The first two bytes should be a 16 bit service uuid associated with the
- * service data.
+ * Returns service data.
*/
public byte[] getServiceData() {
return mServiceData;
@@ -112,7 +111,7 @@
@Override
public String toString() {
- return "AdvertisementData [mServiceUuids=" + mServiceUuids + ", mManufacturerId="
+ return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mManufacturerId="
+ mManufacturerId + ", mManufacturerSpecificData="
+ Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
+ mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData)
@@ -156,15 +155,15 @@
dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0));
}
- public static final Parcelable.Creator<AdvertisementData> CREATOR =
- new Creator<AdvertisementData>() {
+ public static final Parcelable.Creator<AdvertiseData> CREATOR =
+ new Creator<AdvertiseData>() {
@Override
- public AdvertisementData[] newArray(int size) {
- return new AdvertisementData[size];
+ public AdvertiseData[] newArray(int size) {
+ return new AdvertiseData[size];
}
@Override
- public AdvertisementData createFromParcel(Parcel in) {
+ public AdvertiseData createFromParcel(Parcel in) {
Builder builder = new Builder();
if (in.readInt() > 0) {
List<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
@@ -194,7 +193,7 @@
};
/**
- * Builder for {@link AdvertisementData}.
+ * Builder for {@link AdvertiseData}.
*/
public static final class Builder {
private static final int MAX_ADVERTISING_DATA_BYTES = 31;
@@ -215,11 +214,13 @@
private byte[] mServiceData;
/**
- * Set the service uuids. Note the corresponding bluetooth Gatt services need to be already
- * added on the device before start BLE advertising.
+ * Set the service UUIDs.
*
- * @param serviceUuids Service uuids to be advertised, could be 16-bit, 32-bit or 128-bit
- * uuids.
+ * <p><b>Note:</b> The corresponding Bluetooth Gatt services need to already
+ * be added on the device (using {@link android.bluetooth.BluetoothGattServer#addService}) prior
+ * to advertising them.
+ *
+ * @param serviceUuids Service UUIDs to be advertised.
* @throws IllegalArgumentException If the {@code serviceUuids} are null.
*/
public Builder setServiceUuids(List<ParcelUuid> serviceUuids) {
@@ -233,9 +234,8 @@
/**
* Add service data to advertisement.
*
- * @param serviceDataUuid A 16 bit uuid of the service data
- * @param serviceData Service data - the first two bytes of the service data are the service
- * data uuid.
+ * @param serviceDataUuid 16-bit UUID of the service the data is associated with
+ * @param serviceData Service data
* @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is
* empty.
*/
@@ -250,13 +250,14 @@
}
/**
- * Set manufacturer id and data. See <a
- * href="https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers">assigned
- * manufacturer identifies</a> for the existing company identifiers.
+ * Set manufacturer specific data.
*
- * @param manufacturerId Manufacturer id assigned by Bluetooth SIG.
- * @param manufacturerSpecificData Manufacturer specific data - the first two bytes of the
- * manufacturer specific data are the manufacturer id.
+ * <p>Please refer to the Bluetooth Assigned Numbers document provided by the
+ * <a href="https://www.bluetooth.org">Bluetooth SIG</a> for a list of existing
+ * company identifiers.
+ *
+ * @param manufacturerId Manufacturer ID assigned by Bluetooth SIG.
+ * @param manufacturerSpecificData Manufacturer specific data
* @throws IllegalArgumentException If the {@code manufacturerId} is negative or
* {@code manufacturerSpecificData} is null.
*/
@@ -282,16 +283,16 @@
}
/**
- * Build the {@link AdvertisementData}.
+ * Build the {@link AdvertiseData}.
*
* @throws IllegalArgumentException If the data size is larger than 31 bytes.
*/
- public AdvertisementData build() {
+ public AdvertiseData build() {
if (totalBytes() > MAX_ADVERTISING_DATA_BYTES) {
throw new IllegalArgumentException(
"advertisement data size is larger than 31 bytes");
}
- return new AdvertisementData(mServiceUuids,
+ return new AdvertiseData(mServiceUuids,
mServiceDataUuid,
mServiceData, mManufacturerId, mManufacturerSpecificData,
mIncludeTxPowerLevel);
diff --git a/core/java/android/bluetooth/le/AdvertiseSettings.java b/core/java/android/bluetooth/le/AdvertiseSettings.java
index 87d0346..02b4a5b 100644
--- a/core/java/android/bluetooth/le/AdvertiseSettings.java
+++ b/core/java/android/bluetooth/le/AdvertiseSettings.java
@@ -21,7 +21,8 @@
/**
* The {@link AdvertiseSettings} provide a way to adjust advertising preferences for each
- * individual advertisement. Use {@link AdvertiseSettings.Builder} to create an instance.
+ * Bluetooth LE advertisement instance. Use {@link AdvertiseSettings.Builder} to create an
+ * instance of this class.
*/
public final class AdvertiseSettings implements Parcelable {
/**
@@ -29,68 +30,64 @@
* advertising mode as it consumes the least power.
*/
public static final int ADVERTISE_MODE_LOW_POWER = 0;
+
/**
* Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising
* frequency and power consumption.
*/
public static final int ADVERTISE_MODE_BALANCED = 1;
+
/**
* Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power
- * consumption and should not be used for background continuous advertising.
+ * consumption and should not be used for continuous background advertising.
*/
public static final int ADVERTISE_MODE_LOW_LATENCY = 2;
/**
- * Advertise using the lowest transmission(tx) power level. An app can use low transmission
- * power to restrict the visibility range of its advertising packet.
+ * Advertise using the lowest transmission (TX) power level. Low transmission power can be used
+ * to restrict the visibility range of advertising packets.
*/
public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0;
+
/**
- * Advertise using low tx power level.
+ * Advertise using low TX power level.
*/
public static final int ADVERTISE_TX_POWER_LOW = 1;
+
/**
- * Advertise using medium tx power level.
+ * Advertise using medium TX power level.
*/
public static final int ADVERTISE_TX_POWER_MEDIUM = 2;
+
/**
- * Advertise using high tx power level. This is corresponding to largest visibility range of the
+ * Advertise using high TX power level. This corresponds to largest visibility range of the
* advertising packet.
*/
public static final int ADVERTISE_TX_POWER_HIGH = 3;
/**
- * Non-connectable undirected advertising event, as defined in Bluetooth Specification V4.1
- * vol6, part B, section 4.4.2 - Advertising state.
+ * The maximimum limited advertisement duration as specified by the Bluetooth SIG
*/
- public static final int ADVERTISE_TYPE_NON_CONNECTABLE = 0;
- /**
- * Scannable undirected advertise type, as defined in same spec mentioned above. This event type
- * allows a scanner to send a scan request asking additional information about the advertiser.
- */
- public static final int ADVERTISE_TYPE_SCANNABLE = 1;
- /**
- * Connectable undirected advertising type, as defined in same spec mentioned above. This event
- * type allows a scanner to send scan request asking additional information about the
- * advertiser. It also allows an initiator to send a connect request for connection.
- */
- public static final int ADVERTISE_TYPE_CONNECTABLE = 2;
+ private static final int LIMITED_ADVERTISING_MAX_DURATION = 180;
private final int mAdvertiseMode;
private final int mAdvertiseTxPowerLevel;
- private final int mAdvertiseEventType;
+ private final int mAdvertiseTimeoutSeconds;
+ private final boolean mAdvertiseConnectable;
private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel,
- int advertiseEventType) {
+ boolean advertiseConnectable, int advertiseTimeout) {
mAdvertiseMode = advertiseMode;
mAdvertiseTxPowerLevel = advertiseTxPowerLevel;
- mAdvertiseEventType = advertiseEventType;
+ mAdvertiseConnectable = advertiseConnectable;
+ mAdvertiseTimeoutSeconds = advertiseTimeout;
}
private AdvertiseSettings(Parcel in) {
mAdvertiseMode = in.readInt();
mAdvertiseTxPowerLevel = in.readInt();
- mAdvertiseEventType = in.readInt();
+ mAdvertiseConnectable = in.readInt() != 0 ? true : false;
+ mAdvertiseTimeoutSeconds = in.readInt();
}
/**
@@ -101,23 +98,32 @@
}
/**
- * Returns the tx power level for advertising.
+ * Returns the TX power level for advertising.
*/
public int getTxPowerLevel() {
return mAdvertiseTxPowerLevel;
}
/**
- * Returns the advertise event type.
+ * Returns whether the advertisement will indicate connectable.
*/
- public int getType() {
- return mAdvertiseEventType;
+ public boolean getIsConnectable() {
+ return mAdvertiseConnectable;
+ }
+
+ /**
+ * Returns the advertising time limit in seconds.
+ */
+ public int getTimeout() {
+ return mAdvertiseTimeoutSeconds;
}
@Override
public String toString() {
- return "Settings [mAdvertiseMode=" + mAdvertiseMode + ", mAdvertiseTxPowerLevel="
- + mAdvertiseTxPowerLevel + ", mAdvertiseEventType=" + mAdvertiseEventType + "]";
+ return "Settings [mAdvertiseMode=" + mAdvertiseMode
+ + ", mAdvertiseTxPowerLevel=" + mAdvertiseTxPowerLevel
+ + ", mAdvertiseConnectable=" + mAdvertiseConnectable
+ + ", mAdvertiseTimeoutSeconds=" + mAdvertiseTimeoutSeconds + "]";
}
@Override
@@ -129,7 +135,8 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mAdvertiseMode);
dest.writeInt(mAdvertiseTxPowerLevel);
- dest.writeInt(mAdvertiseEventType);
+ dest.writeInt(mAdvertiseConnectable ? 1 : 0);
+ dest.writeInt(mAdvertiseTimeoutSeconds);
}
public static final Parcelable.Creator<AdvertiseSettings> CREATOR =
@@ -151,7 +158,8 @@
public static final class Builder {
private int mMode = ADVERTISE_MODE_LOW_POWER;
private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM;
- private int mType = ADVERTISE_TYPE_NON_CONNECTABLE;
+ private int mTimeoutSeconds = 0;
+ private boolean mConnectable = true;
/**
* Set advertise mode to control the advertising power and latency.
@@ -172,7 +180,7 @@
}
/**
- * Set advertise tx power level to control the transmission power level for the advertising.
+ * Set advertise TX power level to control the transmission power level for the advertising.
*
* @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one of
* {@link AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW},
@@ -191,20 +199,28 @@
}
/**
- * Set advertise type to control the event type of advertising.
+ * Set whether the advertisement type should be connectable or non-connectable.
*
- * @param type Bluetooth LE Advertising type, can be either
- * {@link AdvertiseSettings#ADVERTISE_TYPE_NON_CONNECTABLE},
- * {@link AdvertiseSettings#ADVERTISE_TYPE_SCANNABLE} or
- * {@link AdvertiseSettings#ADVERTISE_TYPE_CONNECTABLE}.
- * @throws IllegalArgumentException If the {@code type} is invalid.
+ * @param isConnectable Controls whether the advertisment type will be connectable (true)
+ * or non-connectable (false).
*/
- public Builder setType(int type) {
- if (type < ADVERTISE_TYPE_NON_CONNECTABLE
- || type > ADVERTISE_TYPE_CONNECTABLE) {
- throw new IllegalArgumentException("unknown advertise type " + type);
+ public Builder setIsConnectable(boolean isConnectable) {
+ mConnectable = isConnectable;
+ return this;
+ }
+
+ /**
+ * Limit advertising to a given amount of time.
+ * @param timeoutSeconds Advertising time limit. May not exceed 180 seconds.
+ * A value of 0 will disable the time limit.
+ * @throws IllegalArgumentException If the provided timeout is over 180s.
+ */
+ public Builder setTimeout(int timeoutSeconds) {
+ if (timeoutSeconds < 0 || timeoutSeconds > LIMITED_ADVERTISING_MAX_DURATION) {
+ throw new IllegalArgumentException("timeoutSeconds invalid (must be 0-"
+ + LIMITED_ADVERTISING_MAX_DURATION + " seconds)");
}
- mType = type;
+ mTimeoutSeconds = timeoutSeconds;
return this;
}
@@ -212,7 +228,7 @@
* Build the {@link AdvertiseSettings} object.
*/
public AdvertiseSettings build() {
- return new AdvertiseSettings(mMode, mTxPowerLevel, mType);
+ return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutSeconds);
}
}
}
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index d395d43..e232512 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -33,17 +33,17 @@
import java.util.UUID;
/**
- * This class provides a way to perform Bluetooth LE advertise operations, such as start and stop
- * advertising. An advertiser can broadcast up to 31 bytes of advertisement data represented by
- * {@link AdvertisementData}.
+ * This class provides a way to perform Bluetooth LE advertise operations, such as starting and
+ * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data
+ * represented by {@link AdvertiseData}.
* <p>
* To get an instance of {@link BluetoothLeAdvertiser}, call the
* {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
* <p>
- * Note most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
* permission.
*
- * @see AdvertisementData
+ * @see AdvertiseData
*/
public final class BluetoothLeAdvertiser {
@@ -57,8 +57,6 @@
/**
* Use BluetoothAdapter.getLeAdvertiser() instead.
- *
- * @param bluetoothManager
* @hide
*/
public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) {
@@ -68,8 +66,8 @@
}
/**
- * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the
- * operation succeeds. Returns immediately, the operation status are delivered through
+ * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be
+ * broadcasted. Returns immediately, the operation status is delivered through
* {@code callback}.
* <p>
* Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
@@ -79,15 +77,15 @@
* @param callback Callback for advertising status.
*/
public void startAdvertising(AdvertiseSettings settings,
- AdvertisementData advertiseData, final AdvertiseCallback callback) {
+ AdvertiseData advertiseData, final AdvertiseCallback callback) {
startAdvertising(settings, advertiseData, null, callback);
}
/**
- * Start Bluetooth LE Advertising. The {@code advertiseData} would be broadcasted after the
- * operation succeeds. The {@code scanResponse} would be returned when the scanning device sends
- * active scan request. Method returns immediately, the operation status are delivered through
- * {@code callback}.
+ * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the
+ * operation succeeds. The {@code scanResponse} is returned when a scanning device sends
+ * an active scan request. This method returns immediately, the operation status is
+ * delivered through {@code callback}.
* <p>
* Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
*
@@ -97,7 +95,7 @@
* @param callback Callback for advertising status.
*/
public void startAdvertising(AdvertiseSettings settings,
- AdvertisementData advertiseData, AdvertisementData scanResponse,
+ AdvertiseData advertiseData, AdvertiseData scanResponse,
final AdvertiseCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
@@ -110,8 +108,8 @@
try {
gatt = mBluetoothManager.getBluetoothGatt();
} catch (RemoteException e) {
- Log.e(TAG, "failed to get bluetooth gatt - ", e);
- postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_CONTROLLER_FAILURE);
+ Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
+ postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
return;
}
if (!mBluetoothAdapter.isMultipleAdvertisementSupported()) {
@@ -127,7 +125,7 @@
mLeAdvertisers.put(callback, wrapper);
}
} catch (RemoteException e) {
- Log.e(TAG, "failed to stop advertising", e);
+ Log.e(TAG, "Failed to stop advertising", e);
}
}
@@ -137,31 +135,24 @@
* <p>
* Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
*
- * @param callback {@link AdvertiseCallback} for delivering stopping advertising status.
+ * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
*/
public void stopAdvertising(final AdvertiseCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
- if (wrapper == null) {
- postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_NOT_STARTED);
- return;
- }
+ if (wrapper == null) return;
+
try {
IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt();
- if (gatt == null) {
- postCallbackFailure(callback,
- AdvertiseCallback.ADVERTISE_FAILED_GATT_SERVICE_FAILURE);
- }
- gatt.stopMultiAdvertising(wrapper.mLeHandle);
+ if (gatt != null) gatt.stopMultiAdvertising(wrapper.mLeHandle);
+
if (wrapper.advertiseStopped()) {
mLeAdvertisers.remove(callback);
}
} catch (RemoteException e) {
- Log.e(TAG, "failed to stop advertising", e);
- postCallbackFailure(callback,
- AdvertiseCallback.ADVERTISE_FAILED_GATT_SERVICE_FAILURE);
+ Log.e(TAG, "Failed to stop advertising", e);
}
}
@@ -171,8 +162,8 @@
private static class AdvertiseCallbackWrapper extends IBluetoothGattCallback.Stub {
private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
private final AdvertiseCallback mAdvertiseCallback;
- private final AdvertisementData mAdvertisement;
- private final AdvertisementData mScanResponse;
+ private final AdvertiseData mAdvertisement;
+ private final AdvertiseData mScanResponse;
private final AdvertiseSettings mSettings;
private final IBluetoothGatt mBluetoothGatt;
@@ -183,7 +174,7 @@
private boolean isAdvertising = false;
public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
- AdvertisementData advertiseData, AdvertisementData scanResponse,
+ AdvertiseData advertiseData, AdvertiseData scanResponse,
AdvertiseSettings settings,
IBluetoothGatt bluetoothGatt) {
mAdvertiseCallback = advertiseCallback;
@@ -347,8 +338,12 @@
@Override
public void onMultiAdvertiseCallback(int status) {
+ // TODO: This logic needs to be re-visited to account
+ // for whether the scan has actually been started
+ // or not. Toggling the isAdvertising does not seem
+ // correct.
synchronized (this) {
- if (status == 0) {
+ if (status == AdvertiseCallback.ADVERTISE_SUCCESS) {
isAdvertising = !isAdvertising;
if (!isAdvertising) {
try {
@@ -357,10 +352,11 @@
} catch (RemoteException e) {
Log.e(TAG, "remote exception when unregistering", e);
}
+ } else {
+ mAdvertiseCallback.onStartSuccess(null);
}
- mAdvertiseCallback.onSuccess(null);
} else {
- mAdvertiseCallback.onFailure(status);
+ if (!isAdvertising) mAdvertiseCallback.onStartFailure(status);
}
notifyAll();
}
@@ -398,7 +394,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- callback.onFailure(error);
+ callback.onStartFailure(error);
}
});
}
diff --git a/core/java/android/bluetooth/le/BluetoothLeScanner.java b/core/java/android/bluetooth/le/BluetoothLeScanner.java
index d5a4728..7e87edc 100644
--- a/core/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/core/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -36,14 +36,14 @@
/**
* This class provides methods to perform scan related operations for Bluetooth LE devices. An
- * application can scan for a particular type of BLE devices using {@link ScanFilter}. It can also
- * request different types of callbacks for delivering the result.
+ * application can scan for a particular type of Bluetotoh LE devices using {@link ScanFilter}.
+ * It can also request different types of callbacks for delivering the result.
* <p>
* Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
* {@link BluetoothLeScanner}.
* <p>
- * Note most of the scan methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
- * permission.
+ * <b>Note:</b> Most of the scan methods here require
+ * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
*
* @see ScanFilter
*/
@@ -58,6 +58,8 @@
private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients;
/**
+ * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
+ * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management
* @hide
*/
public BluetoothLeScanner(IBluetoothManager bluetoothManager) {
@@ -68,13 +70,29 @@
}
/**
+ * Start Bluetooth LE scan with default parameters and no filters.
+ * The scan results will be delivered through {@code callback}.
+ * <p>
+ * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @param callback Callback used to deliver scan results.
+ * @throws IllegalArgumentException If {@code callback} is null.
+ */
+ public void startScan(final ScanCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback is null");
+ }
+ this.startScan(null, new ScanSettings.Builder().build(), callback);
+ }
+
+ /**
* Start Bluetooth LE scan. The scan results will be delivered through {@code callback}.
* <p>
* Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
*
* @param filters {@link ScanFilter}s for finding exact BLE devices.
- * @param settings Settings for ble scan.
- * @param callback Callback when scan results are delivered.
+ * @param settings Settings for the scan.
+ * @param callback Callback used to deliver scan results.
* @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
*/
public void startScan(List<ScanFilter> filters, ScanSettings settings,
@@ -94,7 +112,7 @@
gatt = null;
}
if (gatt == null) {
- postCallbackError(callback, ScanCallback.SCAN_FAILED_GATT_SERVICE_FAILURE);
+ postCallbackError(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
return;
}
if (!isSettingsConfigAllowedForScan(settings)) {
@@ -116,7 +134,7 @@
}
} catch (RemoteException e) {
Log.e(TAG, "GATT service exception when starting scan", e);
- postCallbackError(callback, ScanCallback.SCAN_FAILED_GATT_SERVICE_FAILURE);
+ postCallbackError(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
}
}
}
@@ -139,24 +157,12 @@
}
/**
- * Returns available storage size for batch scan results. It's recommended not to use batch scan
- * if available storage size is small (less than 1k bytes, for instance).
- *
- * @hide TODO: unhide when batching is supported in stack.
- */
- public int getAvailableBatchStorageSizeBytes() {
- throw new UnsupportedOperationException("not impelemented");
- }
-
- /**
* Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth
* LE scan results batched on bluetooth controller. Returns immediately, batch scan results data
* will be delivered through the {@code callback}.
*
* @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
* used to start scan.
- *
- * @hide
*/
public void flushPendingScanResults(ScanCallback callback) {
if (callback == null) {
@@ -302,7 +308,7 @@
handler.post(new Runnable() {
@Override
public void run() {
- mScanCallback.onAdvertisementUpdate(result);
+ mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, result);
}
});
@@ -435,9 +441,9 @@
ScanResult result = new ScanResult(device, advData, rssi,
scanNanos);
if (onFound) {
- mScanCallback.onAdvertisementFound(result);
+ mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, result);
} else {
- mScanCallback.onAdvertisementLost(result);
+ mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, result);
}
}
}
@@ -452,17 +458,13 @@
}
private boolean isSettingsConfigAllowedForScan(ScanSettings settings) {
- boolean ret = true;
- int callbackType;
-
- callbackType = settings.getCallbackType();
- if (((callbackType == ScanSettings.CALLBACK_TYPE_ON_LOST) ||
- (callbackType == ScanSettings.CALLBACK_TYPE_ON_FOUND) ||
- (callbackType == ScanSettings.CALLBACK_TYPE_ON_UPDATE &&
- settings.getReportDelayNanos() > 0) &&
- (!mBluetoothAdapter.isOffloadedFilteringSupported()))) {
- ret = false;
+ final int callbackType = settings.getCallbackType();
+ if (( callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES
+ || (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
+ && settings.getReportDelaySeconds() > 0))
+ && !mBluetoothAdapter.isOffloadedFilteringSupported()) {
+ return false;
}
- return ret;
+ return true;
}
}
diff --git a/core/java/android/bluetooth/le/ScanCallback.java b/core/java/android/bluetooth/le/ScanCallback.java
index 593f7f8..b4c1e17 100644
--- a/core/java/android/bluetooth/le/ScanCallback.java
+++ b/core/java/android/bluetooth/le/ScanCallback.java
@@ -19,64 +19,53 @@
import java.util.List;
/**
- * Callback of Bluetooth LE scans. The results of the scans will be delivered through the callbacks.
+ * Bluetooth LE scan callbacks.
+ * Scan results are reported using these callbacks.
+ *
+ * {@see BluetoothLeScanner#startScan}
*/
public abstract class ScanCallback {
-
/**
* Fails to start scan as BLE scan with the same settings is already started by the app.
*/
public static final int SCAN_FAILED_ALREADY_STARTED = 1;
+
/**
* Fails to start scan as app cannot be registered.
*/
public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2;
+
/**
- * Fails to start scan due to gatt service failure.
+ * Fails to start scan due an internal error
*/
- public static final int SCAN_FAILED_GATT_SERVICE_FAILURE = 3;
- /**
- * Fails to start scan due to controller failure.
- */
- public static final int SCAN_FAILED_CONTROLLER_FAILURE = 4;
+ public static final int SCAN_FAILED_INTERNAL_ERROR = 3;
/**
* Fails to start power optimized scan as this feature is not supported.
*/
- public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 5;
+ public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 4;
/**
- * Callback when a BLE advertisement is found.
+ * Callback when a BLE advertisement has been found.
*
+ * @param callbackType Determines if this callback was triggered because of first match,
+ * a lost match indication or a regular scan result.
* @param result A Bluetooth LE scan result.
*/
- public abstract void onAdvertisementUpdate(ScanResult result);
-
- /**
- * Callback when the BLE advertisement is found for the first time.
- *
- * @param result The Bluetooth LE scan result when the onFound event is triggered.
- */
- public abstract void onAdvertisementFound(ScanResult result);
-
- /**
- * Callback when the BLE advertisement was lost. Note a device has to be "found" before it's
- * lost.
- *
- * @param result The Bluetooth scan result that was last found.
- */
- public abstract void onAdvertisementLost(ScanResult result);
+ public void onScanResult(int callbackType, ScanResult result) {
+ }
/**
* Callback when batch results are delivered.
*
* @param results List of scan results that are previously scanned.
- * @hide
*/
- public abstract void onBatchScanResults(List<ScanResult> results);
+ public void onBatchScanResults(List<ScanResult> results) {
+ }
/**
- * Callback when scan failed.
+ * Callback when scan could not be started.
*/
- public abstract void onScanFailed(int errorCode);
+ public void onScanFailed(int errorCode) {
+ }
}
diff --git a/core/java/android/bluetooth/le/ScanFilter.java b/core/java/android/bluetooth/le/ScanFilter.java
index c2e316b..e8a91b8 100644
--- a/core/java/android/bluetooth/le/ScanFilter.java
+++ b/core/java/android/bluetooth/le/ScanFilter.java
@@ -45,10 +45,10 @@
public final class ScanFilter implements Parcelable {
@Nullable
- private final String mLocalName;
+ private final String mDeviceName;
@Nullable
- private final String mMacAddress;
+ private final String mDeviceAddress;
@Nullable
private final ParcelUuid mServiceUuid;
@@ -69,14 +69,14 @@
private final int mMinRssi;
private final int mMaxRssi;
- private ScanFilter(String name, String macAddress, ParcelUuid uuid,
+ private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
ParcelUuid uuidMask, byte[] serviceData, byte[] serviceDataMask,
int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
int minRssi, int maxRssi) {
- mLocalName = name;
+ mDeviceName = name;
mServiceUuid = uuid;
mServiceUuidMask = uuidMask;
- mMacAddress = macAddress;
+ mDeviceAddress = deviceAddress;
mServiceData = serviceData;
mServiceDataMask = serviceDataMask;
mManufacturerId = manufacturerId;
@@ -93,13 +93,13 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mLocalName == null ? 0 : 1);
- if (mLocalName != null) {
- dest.writeString(mLocalName);
+ dest.writeInt(mDeviceName == null ? 0 : 1);
+ if (mDeviceName != null) {
+ dest.writeString(mDeviceName);
}
- dest.writeInt(mMacAddress == null ? 0 : 1);
- if (mMacAddress != null) {
- dest.writeString(mMacAddress);
+ dest.writeInt(mDeviceAddress == null ? 0 : 1);
+ if (mDeviceAddress != null) {
+ dest.writeString(mDeviceAddress);
}
dest.writeInt(mServiceUuid == null ? 0 : 1);
if (mServiceUuid != null) {
@@ -145,10 +145,10 @@
public ScanFilter createFromParcel(Parcel in) {
Builder builder = new Builder();
if (in.readInt() == 1) {
- builder.setName(in.readString());
+ builder.setDeviceName(in.readString());
}
if (in.readInt() == 1) {
- builder.setMacAddress(in.readString());
+ builder.setDeviceAddress(in.readString());
}
if (in.readInt() == 1) {
ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
@@ -196,11 +196,11 @@
};
/**
- * Returns the filter set the local name field of Bluetooth advertisement data.
+ * Returns the filter set the device name field of Bluetooth advertisement data.
*/
@Nullable
- public String getLocalName() {
- return mLocalName;
+ public String getDeviceName() {
+ return mDeviceName;
}
/**
@@ -218,7 +218,7 @@
@Nullable
public String getDeviceAddress() {
- return mMacAddress;
+ return mDeviceAddress;
}
@Nullable
@@ -249,14 +249,14 @@
}
/**
- * Returns minimum value of rssi for the scan filter. {@link Integer#MIN_VALUE} if not set.
+ * Returns minimum value of RSSI for the scan filter. {@link Integer#MIN_VALUE} if not set.
*/
public int getMinRssi() {
return mMinRssi;
}
/**
- * Returns maximum value of the rssi for the scan filter. {@link Integer#MAX_VALUE} if not set.
+ * Returns maximum value of the RSSI for the scan filter. {@link Integer#MAX_VALUE} if not set.
*/
public int getMaxRssi() {
return mMaxRssi;
@@ -272,7 +272,7 @@
}
BluetoothDevice device = scanResult.getDevice();
// Device match.
- if (mMacAddress != null && (device == null || !mMacAddress.equals(device.getAddress()))) {
+ if (mDeviceAddress != null && (device == null || !mDeviceAddress.equals(device.getAddress()))) {
return false;
}
@@ -286,13 +286,13 @@
// Scan record is null but there exist filters on it.
if (scanRecord == null
- && (mLocalName != null || mServiceUuid != null || mManufacturerData != null
+ && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null
|| mServiceData != null)) {
return false;
}
// Local name match.
- if (mLocalName != null && !mLocalName.equals(scanRecord.getLocalName())) {
+ if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) {
return false;
}
@@ -367,7 +367,7 @@
@Override
public String toString() {
- return "BluetoothLeScanFilter [mLocalName=" + mLocalName + ", mMacAddress=" + mMacAddress
+ return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress=" + mDeviceAddress
+ ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask + ", mServiceData="
+ Arrays.toString(mServiceData) + ", mServiceDataMask="
+ Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
@@ -378,7 +378,7 @@
@Override
public int hashCode() {
- return Objects.hash(mLocalName, mMacAddress, mManufacturerId, mManufacturerData,
+ return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId, mManufacturerData,
mManufacturerDataMask, mMaxRssi, mMinRssi, mServiceData, mServiceDataMask,
mServiceUuid, mServiceUuidMask);
}
@@ -392,8 +392,8 @@
return false;
}
ScanFilter other = (ScanFilter) obj;
- return Objects.equals(mLocalName, other.mLocalName) &&
- Objects.equals(mMacAddress, other.mMacAddress) &&
+ return Objects.equals(mDeviceName, other.mDeviceName) &&
+ Objects.equals(mDeviceAddress, other.mDeviceAddress) &&
mManufacturerId == other.mManufacturerId &&
Objects.deepEquals(mManufacturerData, other.mManufacturerData) &&
Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) &&
@@ -409,8 +409,8 @@
*/
public static final class Builder {
- private String mLocalName;
- private String mMacAddress;
+ private String mDeviceName;
+ private String mDeviceAddress;
private ParcelUuid mServiceUuid;
private ParcelUuid mUuidMask;
@@ -426,26 +426,26 @@
private int mMaxRssi = Integer.MAX_VALUE;
/**
- * Set filter on local name.
+ * Set filter on device name.
*/
- public Builder setName(String localName) {
- mLocalName = localName;
+ public Builder setDeviceName(String deviceName) {
+ mDeviceName = deviceName;
return this;
}
/**
- * Set filter on device mac address.
+ * Set filter on device address.
*
- * @param macAddress The device mac address for the filter. It needs to be in the format of
- * "01:02:03:AB:CD:EF". The mac address can be validated using
+ * @param deviceAddress The device Bluetooth address for the filter. It needs to be in
+ * the format of "01:02:03:AB:CD:EF". The device address can be validated using
* {@link BluetoothAdapter#checkBluetoothAddress}.
- * @throws IllegalArgumentException If the {@code macAddress} is invalid.
+ * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
*/
- public Builder setMacAddress(String macAddress) {
- if (macAddress != null && !BluetoothAdapter.checkBluetoothAddress(macAddress)) {
- throw new IllegalArgumentException("invalid mac address " + macAddress);
+ public Builder setDeviceAddress(String deviceAddress) {
+ if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
+ throw new IllegalArgumentException("invalid device address " + deviceAddress);
}
- mMacAddress = macAddress;
+ mDeviceAddress = deviceAddress;
return this;
}
@@ -564,7 +564,7 @@
}
/**
- * Set the desired rssi range for the filter. A scan result with rssi in the range of
+ * Set the desired RSSI range for the filter. A scan result with RSSI in the range of
* [minRssi, maxRssi] will be consider as a match.
*/
public Builder setRssiRange(int minRssi, int maxRssi) {
@@ -579,7 +579,7 @@
* @throws IllegalArgumentException If the filter cannot be built.
*/
public ScanFilter build() {
- return new ScanFilter(mLocalName, mMacAddress,
+ return new ScanFilter(mDeviceName, mDeviceAddress,
mServiceUuid, mUuidMask,
mServiceData, mServiceDataMask,
mManufacturerId, mManufacturerData, mManufacturerDataMask, mMinRssi, mMaxRssi);
diff --git a/core/java/android/bluetooth/le/ScanRecord.java b/core/java/android/bluetooth/le/ScanRecord.java
index bd7304b..fc46e76 100644
--- a/core/java/android/bluetooth/le/ScanRecord.java
+++ b/core/java/android/bluetooth/le/ScanRecord.java
@@ -66,7 +66,7 @@
private final int mTxPowerLevel;
// Local name of the Bluetooth LE device.
- private final String mLocalName;
+ private final String mDeviceName;
/**
* Returns the advertising flags indicating the discoverable mode and capability of the device.
@@ -77,7 +77,7 @@
}
/**
- * Returns a list of service uuids within the advertisement that are used to identify the
+ * Returns a list of service UUIDs within the advertisement that are used to identify the
* bluetooth gatt services.
*/
public List<ParcelUuid> getServiceUuids() {
@@ -94,22 +94,21 @@
/**
* Returns the manufacturer specific data which is the content of manufacturer specific data
- * field. The first 2 bytes of the data contain the company id.
+ * field.
*/
public byte[] getManufacturerSpecificData() {
return mManufacturerSpecificData;
}
/**
- * Returns a 16 bit uuid of the service that the service data is associated with.
+ * Returns a 16-bit UUID of the service that the service data is associated with.
*/
public ParcelUuid getServiceDataUuid() {
return mServiceDataUuid;
}
/**
- * Returns service data. The first two bytes should be a 16 bit service uuid associated with the
- * service data.
+ * Returns service data.
*/
public byte[] getServiceData() {
return mServiceData;
@@ -130,8 +129,8 @@
* Returns the local name of the BLE device. The is a UTF-8 encoded string.
*/
@Nullable
- public String getLocalName() {
- return mLocalName;
+ public String getDeviceName() {
+ return mDeviceName;
}
private ScanRecord(List<ParcelUuid> serviceUuids,
@@ -144,7 +143,7 @@
mManufacturerSpecificData = manufacturerSpecificData;
mServiceDataUuid = serviceDataUuid;
mServiceData = serviceData;
- mLocalName = localName;
+ mDeviceName = localName;
mAdvertiseFlags = advertiseFlags;
mTxPowerLevel = txPowerLevel;
}
@@ -155,7 +154,7 @@
+ ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData="
+ Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
+ mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData)
- + ", mTxPowerLevel=" + mTxPowerLevel + ", mLocalName=" + mLocalName + "]";
+ + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
}
/**
@@ -223,7 +222,7 @@
break;
case DATA_TYPE_SERVICE_DATA:
serviceData = extractBytes(scanRecord, currentPos, dataLength);
- // The first two bytes of the service data are service data uuid.
+ // The first two bytes of the service data are service data UUID.
int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
serviceUuidLength);
@@ -256,7 +255,7 @@
}
}
- // Parse service uuids.
+ // Parse service UUIDs.
private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
int uuidLength, List<ParcelUuid> serviceUuids) {
while (dataLength > 0) {
diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java
index 7c0f9e1..2097702 100644
--- a/core/java/android/bluetooth/le/ScanSettings.java
+++ b/core/java/android/bluetooth/le/ScanSettings.java
@@ -16,54 +16,66 @@
package android.bluetooth.le;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
/**
- * Settings for Bluetooth LE scan.
+ * Bluetooth LE scan settings are passed to {@link BluetoothLeScanner#startScan}
+ * to define the parameters for the scan.
*/
public final class ScanSettings implements Parcelable {
/**
- * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the
- * least power.
+ * Perform Bluetooth LE scan in low power mode.
+ * This is the default scan mode as it consumes the least power.
*/
public static final int SCAN_MODE_LOW_POWER = 0;
+
/**
* Perform Bluetooth LE scan in balanced power mode.
+ * Scan results are returned at a rate that provides a good trade-off between scan
+ * frequency and power consumption.
*/
public static final int SCAN_MODE_BALANCED = 1;
+
/**
- * Scan using highest duty cycle. It's recommended only using this mode when the application is
- * running in foreground.
+ * Scan using highest duty cycle.
+ * It's recommended to only use this mode when the application is running in the foreground.
*/
public static final int SCAN_MODE_LOW_LATENCY = 2;
/**
- * Callback each time when a bluetooth advertisement is found.
+ * Triggger a callback for every Bluetooth advertisement found that matches the
+ * filter criteria. If no filter is active, all advertisement packets are reported.
*/
- public static final int CALLBACK_TYPE_ON_UPDATE = 0;
- /**
- * Callback when a bluetooth advertisement is found for the first time.
- */
- public static final int CALLBACK_TYPE_ON_FOUND = 1;
- /**
- * Callback when a bluetooth advertisement is found for the first time, then lost.
- */
- public static final int CALLBACK_TYPE_ON_LOST = 2;
+ public static final int CALLBACK_TYPE_ALL_MATCHES = 1;
/**
- * Full scan result which contains device mac address, rssi, advertising and scan response and
- * scan timestamp.
+ * A result callback is only triggered for the first advertisement packet received that
+ * matches the filter criteria.
+ */
+ public static final int CALLBACK_TYPE_FIRST_MATCH = 2;
+
+ /**
+ * Receive a callback when advertisements are no longer received from a device that has been
+ * previously reported by a first match callback.
+ */
+ public static final int CALLBACK_TYPE_MATCH_LOST = 4;
+
+ /**
+ * Request full scan results which contain the device, rssi, advertising data, scan response
+ * as well as the scan timestamp.
*/
public static final int SCAN_RESULT_TYPE_FULL = 0;
+
/**
- * Truncated scan result which contains device mac address, rssi and scan timestamp. Note it's
- * possible for an app to get more scan results that it asks if there are multiple apps using
- * this type. TODO: decide whether we could unhide this setting.
- *
+ * Request abbreviated scan results which contain the device, rssi and scan timestamp.
+ * <p><b>Note:</b> It is possible for an application to get more scan results than
+ * it asked for, if there are multiple apps using this type.
* @hide
*/
- public static final int SCAN_RESULT_TYPE_TRUNCATED = 1;
+ @SystemApi
+ public static final int SCAN_RESULT_TYPE_ABBREVIATED = 1;
// Bluetooth LE scan mode.
private int mScanMode;
@@ -75,7 +87,7 @@
private int mScanResultType;
// Time of delay for reporting the scan result
- private long mReportDelayNanos;
+ private long mReportDelaySeconds;
public int getScanMode() {
return mScanMode;
@@ -92,23 +104,23 @@
/**
* Returns report delay timestamp based on the device clock.
*/
- public long getReportDelayNanos() {
- return mReportDelayNanos;
+ public long getReportDelaySeconds() {
+ return mReportDelaySeconds;
}
private ScanSettings(int scanMode, int callbackType, int scanResultType,
- long reportDelayNanos) {
+ long reportDelaySeconds) {
mScanMode = scanMode;
mCallbackType = callbackType;
mScanResultType = scanResultType;
- mReportDelayNanos = reportDelayNanos;
+ mReportDelaySeconds = reportDelaySeconds;
}
private ScanSettings(Parcel in) {
mScanMode = in.readInt();
mCallbackType = in.readInt();
mScanResultType = in.readInt();
- mReportDelayNanos = in.readLong();
+ mReportDelaySeconds = in.readLong();
}
@Override
@@ -116,7 +128,7 @@
dest.writeInt(mScanMode);
dest.writeInt(mCallbackType);
dest.writeInt(mScanResultType);
- dest.writeLong(mReportDelayNanos);
+ dest.writeLong(mReportDelaySeconds);
}
@Override
@@ -142,15 +154,14 @@
*/
public static final class Builder {
private int mScanMode = SCAN_MODE_LOW_POWER;
- private int mCallbackType = CALLBACK_TYPE_ON_UPDATE;
+ private int mCallbackType = CALLBACK_TYPE_ALL_MATCHES;
private int mScanResultType = SCAN_RESULT_TYPE_FULL;
- private long mReportDelayNanos = 0;
+ private long mReportDelaySeconds = 0;
/**
* Set scan mode for Bluetooth LE scan.
*
- * @param scanMode The scan mode can be one of
- * {@link ScanSettings#SCAN_MODE_LOW_POWER},
+ * @param scanMode The scan mode can be one of {@link ScanSettings#SCAN_MODE_LOW_POWER},
* {@link ScanSettings#SCAN_MODE_BALANCED} or
* {@link ScanSettings#SCAN_MODE_LOW_LATENCY}.
* @throws IllegalArgumentException If the {@code scanMode} is invalid.
@@ -166,13 +177,13 @@
/**
* Set callback type for Bluetooth LE scan.
*
- * @param callbackType The callback type for the scan. Can only be
- * {@link ScanSettings#CALLBACK_TYPE_ON_UPDATE}.
+ * @param callbackType The callback type flags for the scan.
* @throws IllegalArgumentException If the {@code callbackType} is invalid.
*/
public Builder setCallbackType(int callbackType) {
- if (callbackType < CALLBACK_TYPE_ON_UPDATE
- || callbackType > CALLBACK_TYPE_ON_LOST) {
+ if (callbackType < CALLBACK_TYPE_ALL_MATCHES
+ || callbackType > (CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST)
+ || (callbackType & CALLBACK_TYPE_ALL_MATCHES) != CALLBACK_TYPE_ALL_MATCHES) {
throw new IllegalArgumentException("invalid callback type - " + callbackType);
}
mCallbackType = callbackType;
@@ -184,14 +195,14 @@
*
* @param scanResultType Type for scan result, could be either
* {@link ScanSettings#SCAN_RESULT_TYPE_FULL} or
- * {@link ScanSettings#SCAN_RESULT_TYPE_TRUNCATED}.
+ * {@link ScanSettings#SCAN_RESULT_TYPE_ABBREVIATED}.
* @throws IllegalArgumentException If the {@code scanResultType} is invalid.
- *
* @hide
*/
+ @SystemApi
public Builder setScanResultType(int scanResultType) {
if (scanResultType < SCAN_RESULT_TYPE_FULL
- || scanResultType > SCAN_RESULT_TYPE_TRUNCATED) {
+ || scanResultType > SCAN_RESULT_TYPE_ABBREVIATED) {
throw new IllegalArgumentException(
"invalid scanResultType - " + scanResultType);
}
@@ -201,9 +212,13 @@
/**
* Set report delay timestamp for Bluetooth LE scan.
+ * @param reportDelaySeconds Set to 0 to be notified of results immediately.
+ * Values >0 causes the scan results to be queued
+ * up and delivered after the requested delay or when
+ * the internal buffers fill up.
*/
- public Builder setReportDelayNanos(long reportDelayNanos) {
- mReportDelayNanos = reportDelayNanos;
+ public Builder setReportDelaySeconds(long reportDelaySeconds) {
+ mReportDelaySeconds = reportDelaySeconds;
return this;
}
@@ -212,7 +227,7 @@
*/
public ScanSettings build() {
return new ScanSettings(mScanMode, mCallbackType, mScanResultType,
- mReportDelayNanos);
+ mReportDelaySeconds);
}
}
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e24dc84..4153a02 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3810,6 +3810,18 @@
*/
public static final int FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS = 0x00002000;
/**
+ * If set along with FLAG_ACTIVITY_NEW_DOCUMENT then the task being launched will not be
+ * presented to the user but will instead be only available through the recents task list.
+ * In addition, the new task wil be affiliated with the launching activity's task.
+ * Affiliated tasks are grouped together in the recents task list.
+ *
+ * <p>This behavior is not supported for activities with {@link
+ * android.R.styleable#AndroidManifestActivity_launchMode launchMode} values of
+ * <code>singleInstance</code> or <code>singleTask</code>.
+ */
+ public static final int FLAG_ACTIVITY_LAUNCH_BEHIND = 0x00001000;
+
+ /**
* If set, when sending a broadcast only registered receivers will be
* called -- no BroadcastReceiver components will be launched.
*/
diff --git a/core/java/android/content/pm/IPackageInstallObserver2.aidl b/core/java/android/content/pm/IPackageInstallObserver2.aidl
index 7205ce7..824d730 100644
--- a/core/java/android/content/pm/IPackageInstallObserver2.aidl
+++ b/core/java/android/content/pm/IPackageInstallObserver2.aidl
@@ -40,5 +40,5 @@
* </tr>
* </table>
*/
- void packageInstalled(String basePackageName, in Bundle extras, int returnCode);
+ void packageInstalled(String basePackageName, in Bundle extras, int returnCode, String msg);
}
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 4d6ee64..32460c9 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -17,17 +17,22 @@
package android.content.pm;
import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstallerObserver;
import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
import android.os.ParcelFileDescriptor;
/** {@hide} */
interface IPackageInstaller {
- int createSession(String installerPackageName, in PackageInstallerParams params, int userId);
+ int createSession(String installerPackageName, in InstallSessionParams params, int userId);
IPackageInstallerSession openSession(int sessionId);
- int[] getSessions(String installerPackageName, int userId);
+ List<InstallSessionInfo> getSessions(int userId);
- void uninstall(String basePackageName, int flags, in IPackageDeleteObserver observer, int userId);
- void uninstallSplit(String basePackageName, String splitName, int flags, in IPackageDeleteObserver observer, int userId);
+ void registerObserver(IPackageInstallerObserver observer, int userId);
+ void unregisterObserver(IPackageInstallerObserver observer, int userId);
+
+ void uninstall(String packageName, int flags, in IPackageDeleteObserver observer, int userId);
+ void uninstallSplit(String packageName, String splitName, int flags, in IPackageDeleteObserver observer, int userId);
}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/content/pm/IPackageInstallerObserver.aidl
similarity index 66%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to core/java/android/content/pm/IPackageInstallerObserver.aidl
index 3da1321..85660e4 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/content/pm/IPackageInstallerObserver.aidl
@@ -14,6 +14,13 @@
* limitations under the License.
*/
-package android.bluetooth.le;
+package android.content.pm;
-parcelable AdvertisementData;
\ No newline at end of file
+import android.content.pm.InstallSessionInfo;
+
+/** {@hide} */
+oneway interface IPackageInstallerObserver {
+ void onSessionCreated(in InstallSessionInfo info);
+ void onSessionProgress(int sessionId, int progress);
+ void onSessionFinished(int sessionId, boolean success);
+}
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index f881acd..d6775d4 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -21,7 +21,8 @@
/** {@hide} */
interface IPackageInstallerSession {
- void updateProgress(int progress);
+ void setClientProgress(int progress);
+ void addClientProgress(int progress);
ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes);
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/content/pm/InstallSessionInfo.aidl
similarity index 90%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to core/java/android/content/pm/InstallSessionInfo.aidl
index 3da1321..3d21bbd 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/content/pm/InstallSessionInfo.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.bluetooth.le;
+package android.content.pm;
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable InstallSessionInfo;
diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java
new file mode 100644
index 0000000..45606d1
--- /dev/null
+++ b/core/java/android/content/pm/InstallSessionInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/** {@hide} */
+public class InstallSessionInfo implements Parcelable {
+ public int sessionId;
+ public String installerPackageName;
+ public int progress;
+
+ public boolean fullInstall;
+ public String packageName;
+ public Bitmap icon;
+ public CharSequence title;
+
+ /** {@hide} */
+ public InstallSessionInfo() {
+ }
+
+ /** {@hide} */
+ public InstallSessionInfo(Parcel source) {
+ sessionId = source.readInt();
+ installerPackageName = source.readString();
+ progress = source.readInt();
+
+ fullInstall = source.readInt() != 0;
+ packageName = source.readString();
+ icon = source.readParcelable(null);
+ title = source.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(sessionId);
+ dest.writeString(installerPackageName);
+ dest.writeInt(progress);
+
+ dest.writeInt(fullInstall ? 1 : 0);
+ dest.writeString(packageName);
+ dest.writeParcelable(icon, flags);
+ dest.writeString(title != null ? title.toString() : null);
+ }
+
+ public static final Parcelable.Creator<InstallSessionInfo>
+ CREATOR = new Parcelable.Creator<InstallSessionInfo>() {
+ @Override
+ public InstallSessionInfo createFromParcel(Parcel p) {
+ return new InstallSessionInfo(p);
+ }
+
+ @Override
+ public InstallSessionInfo[] newArray(int size) {
+ return new InstallSessionInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/content/pm/InstallSessionParams.aidl
similarity index 90%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to core/java/android/content/pm/InstallSessionParams.aidl
index 3da1321..81b7574 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/content/pm/InstallSessionParams.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.bluetooth.le;
+package android.content.pm;
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable InstallSessionParams;
diff --git a/core/java/android/content/pm/PackageInstallerParams.java b/core/java/android/content/pm/InstallSessionParams.java
similarity index 62%
rename from core/java/android/content/pm/PackageInstallerParams.java
rename to core/java/android/content/pm/InstallSessionParams.java
index 527edee..43e3314 100644
--- a/core/java/android/content/pm/PackageInstallerParams.java
+++ b/core/java/android/content/pm/InstallSessionParams.java
@@ -21,12 +21,10 @@
import android.os.Parcel;
import android.os.Parcelable;
-/**
- * Parameters that define an installation session.
- *
- * {@hide}
- */
-public class PackageInstallerParams implements Parcelable {
+import com.android.internal.util.IndentingPrintWriter;
+
+/** {@hide} */
+public class InstallSessionParams implements Parcelable {
// TODO: extend to support remaining VerificationParams
@@ -41,11 +39,13 @@
/** {@hide} */
public long deltaSize = -1;
/** {@hide} */
- public String basePackageName;
+ public int progressMax = 100;
+ /** {@hide} */
+ public String packageName;
/** {@hide} */
public Bitmap icon;
/** {@hide} */
- public String title;
+ public CharSequence title;
/** {@hide} */
public Uri originatingUri;
/** {@hide} */
@@ -53,20 +53,19 @@
/** {@hide} */
public String abiOverride;
- public PackageInstallerParams() {
+ public InstallSessionParams() {
}
/** {@hide} */
- public PackageInstallerParams(Parcel source) {
+ public InstallSessionParams(Parcel source) {
fullInstall = source.readInt() != 0;
installFlags = source.readInt();
installLocation = source.readInt();
signatures = (Signature[]) source.readParcelableArray(null);
deltaSize = source.readLong();
- basePackageName = source.readString();
- if (source.readInt() != 0) {
- icon = Bitmap.CREATOR.createFromParcel(source);
- }
+ progressMax = source.readInt();
+ packageName = source.readString();
+ icon = source.readParcelable(null);
title = source.readString();
originatingUri = source.readParcelable(null);
referrerUri = source.readParcelable(null);
@@ -93,8 +92,12 @@
this.deltaSize = deltaSize;
}
- public void setBasePackageName(String basePackageName) {
- this.basePackageName = basePackageName;
+ public void setProgressMax(int progressMax) {
+ this.progressMax = progressMax;
+ }
+
+ public void setPackageName(String packageName) {
+ this.packageName = packageName;
}
public void setIcon(Bitmap icon) {
@@ -102,7 +105,7 @@
}
public void setTitle(CharSequence title) {
- this.title = (title != null) ? title.toString() : null;
+ this.title = title;
}
public void setOriginatingUri(Uri originatingUri) {
@@ -113,6 +116,23 @@
this.referrerUri = referrerUri;
}
+ /** {@hide} */
+ public void dump(IndentingPrintWriter pw) {
+ pw.printPair("fullInstall", fullInstall);
+ pw.printHexPair("installFlags", installFlags);
+ pw.printPair("installLocation", installLocation);
+ pw.printPair("signatures", (signatures != null));
+ pw.printPair("deltaSize", deltaSize);
+ pw.printPair("progressMax", progressMax);
+ pw.printPair("packageName", packageName);
+ pw.printPair("icon", (icon != null));
+ pw.printPair("title", title);
+ pw.printPair("originatingUri", originatingUri);
+ pw.printPair("referrerUri", referrerUri);
+ pw.printPair("abiOverride", abiOverride);
+ pw.println();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -125,29 +145,25 @@
dest.writeInt(installLocation);
dest.writeParcelableArray(signatures, flags);
dest.writeLong(deltaSize);
- dest.writeString(basePackageName);
- if (icon != null) {
- dest.writeInt(1);
- icon.writeToParcel(dest, flags);
- } else {
- dest.writeInt(0);
- }
- dest.writeString(title);
+ dest.writeInt(progressMax);
+ dest.writeString(packageName);
+ dest.writeParcelable(icon, flags);
+ dest.writeString(title != null ? title.toString() : null);
dest.writeParcelable(originatingUri, flags);
dest.writeParcelable(referrerUri, flags);
dest.writeString(abiOverride);
}
- public static final Parcelable.Creator<PackageInstallerParams>
- CREATOR = new Parcelable.Creator<PackageInstallerParams>() {
+ public static final Parcelable.Creator<InstallSessionParams>
+ CREATOR = new Parcelable.Creator<InstallSessionParams>() {
@Override
- public PackageInstallerParams createFromParcel(Parcel p) {
- return new PackageInstallerParams(p);
+ public InstallSessionParams createFromParcel(Parcel p) {
+ return new InstallSessionParams(p);
}
@Override
- public PackageInstallerParams[] newArray(int size) {
- return new PackageInstallerParams[size];
+ public InstallSessionParams[] newArray(int size) {
+ return new InstallSessionParams[size];
}
};
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index a60a2fe..401be06 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -19,12 +19,16 @@
import android.app.PackageInstallObserver;
import android.app.PackageUninstallObserver;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
import android.os.FileBridge;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.util.ExceptionUtils;
import java.io.Closeable;
+import java.io.IOException;
import java.io.OutputStream;
+import java.util.List;
/** {@hide} */
public class PackageInstaller {
@@ -42,9 +46,9 @@
mUserId = userId;
}
- public boolean isPackageAvailable(String basePackageName) {
+ public boolean isPackageAvailable(String packageName) {
try {
- final ApplicationInfo info = mPm.getApplicationInfo(basePackageName,
+ final ApplicationInfo info = mPm.getApplicationInfo(packageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
return ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
} catch (NameNotFoundException e) {
@@ -52,19 +56,22 @@
}
}
- public void installAvailablePackage(String basePackageName, PackageInstallObserver observer) {
+ public void installAvailablePackage(String packageName, PackageInstallObserver observer) {
int returnCode;
try {
- returnCode = mPm.installExistingPackage(basePackageName);
+ returnCode = mPm.installExistingPackage(packageName);
} catch (NameNotFoundException e) {
returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
}
- observer.packageInstalled(basePackageName, null, returnCode);
+ observer.packageInstalled(packageName, null, returnCode);
}
- public int createSession(PackageInstallerParams params) {
+ public int createSession(InstallSessionParams params) throws IOException {
try {
return mInstaller.createSession(mInstallerPackageName, params, mUserId);
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -78,26 +85,73 @@
}
}
- public int[] getSessions() {
+ public List<InstallSessionInfo> getSessions() {
+ // TODO: filter based on caller
+ // TODO: let launcher app see all active sessions
try {
- return mInstaller.getSessions(mInstallerPackageName, mUserId);
+ return mInstaller.getSessions(mUserId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
- public void uninstall(String basePackageName, PackageUninstallObserver observer) {
+ public void uninstall(String packageName, UninstallResultCallback callback) {
try {
- mInstaller.uninstall(basePackageName, 0, observer.getBinder(), mUserId);
+ mInstaller.uninstall(packageName, 0,
+ new UninstallResultCallbackDelegate(callback).getBinder(), mUserId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
}
- public void uninstall(String basePackageName, String splitName,
- PackageUninstallObserver observer) {
+ public void uninstall(String packageName, String splitName, UninstallResultCallback callback) {
try {
- mInstaller.uninstallSplit(basePackageName, splitName, 0, observer.getBinder(), mUserId);
+ mInstaller.uninstallSplit(packageName, splitName, 0,
+ new UninstallResultCallbackDelegate(callback).getBinder(), mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public static abstract class SessionObserver {
+ private final IPackageInstallerObserver.Stub mBinder = new IPackageInstallerObserver.Stub() {
+ @Override
+ public void onSessionCreated(InstallSessionInfo info) {
+ SessionObserver.this.onCreated(info);
+ }
+
+ @Override
+ public void onSessionProgress(int sessionId, int progress) {
+ SessionObserver.this.onProgress(sessionId, progress);
+ }
+
+ @Override
+ public void onSessionFinished(int sessionId, boolean success) {
+ SessionObserver.this.onFinished(sessionId, success);
+ }
+ };
+
+ /** {@hide} */
+ public IPackageInstallerObserver getBinder() {
+ return mBinder;
+ }
+
+ public abstract void onCreated(InstallSessionInfo info);
+ public abstract void onProgress(int sessionId, int progress);
+ public abstract void onFinished(int sessionId, boolean success);
+ }
+
+ public void registerSessionObserver(SessionObserver observer) {
+ try {
+ mInstaller.registerObserver(observer.getBinder(), mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ public void unregisterSessionObserver(SessionObserver observer) {
+ try {
+ mInstaller.unregisterObserver(observer.getBinder(), mUserId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -123,9 +177,18 @@
mSession = session;
}
- public void updateProgress(int progress) {
+ public void setProgress(int progress) {
try {
- mSession.updateProgress(progress);
+ mSession.setClientProgress(progress);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
+ public void addProgress(int progress) {
+ try {
+ mSession.addClientProgress(progress);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -137,19 +200,31 @@
* {@link OutputStream#flush()} to ensure bytes have been written to
* disk.
*/
- public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes) {
+ public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes)
+ throws IOException {
try {
final ParcelFileDescriptor clientSocket = mSession.openWrite(splitName,
offsetBytes, lengthBytes);
return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor());
+ } catch (RuntimeException e) {
+ ExceptionUtils.maybeUnwrapIOException(e);
+ throw e;
} catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
+ throw new IOException(e);
}
}
- public void install(PackageInstallObserver observer) {
+ public void fsync(OutputStream out) throws IOException {
+ if (out instanceof FileBridge.FileBridgeOutputStream) {
+ ((FileBridge.FileBridgeOutputStream) out).fsync();
+ } else {
+ throw new IllegalArgumentException("Unrecognized stream");
+ }
+ }
+
+ public void commit(CommitResultCallback callback) {
try {
- mSession.install(observer.getBinder());
+ mSession.install(new CommitResultCallbackDelegate(callback).getBinder());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -168,4 +243,135 @@
}
}
}
+
+ public static abstract class UninstallResultCallback {
+ public abstract void onSuccess();
+ public abstract void onFailure(String msg);
+ }
+
+ private static class UninstallResultCallbackDelegate extends PackageUninstallObserver {
+ private final UninstallResultCallback target;
+
+ public UninstallResultCallbackDelegate(UninstallResultCallback target) {
+ this.target = target;
+ }
+
+ @Override
+ public void onUninstallFinished(String basePackageName, int returnCode) {
+ final String msg = null;
+
+ switch (returnCode) {
+ case PackageManager.DELETE_SUCCEEDED: target.onSuccess(); break;
+ case PackageManager.DELETE_FAILED_INTERNAL_ERROR: target.onFailure("DELETE_FAILED_INTERNAL_ERROR: " + msg); break;
+ case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: target.onFailure("DELETE_FAILED_DEVICE_POLICY_MANAGER: " + msg); break;
+ case PackageManager.DELETE_FAILED_USER_RESTRICTED: target.onFailure("DELETE_FAILED_USER_RESTRICTED: " + msg); break;
+ case PackageManager.DELETE_FAILED_OWNER_BLOCKED: target.onFailure("DELETE_FAILED_OWNER_BLOCKED: " + msg); break;
+ default: target.onFailure(msg); break;
+ }
+ }
+ }
+
+ public static abstract class CommitResultCallback {
+ public abstract void onSuccess();
+ public abstract void onFailure(String msg);
+
+ /**
+ * One or more of the APKs included in the session was invalid. For
+ * example, they might be malformed, corrupt, incorrectly signed,
+ * mismatched, etc. The installer may want to try downloading and
+ * installing again.
+ */
+ public void onFailureInvalid(String msg) {
+ onFailure(msg);
+ }
+
+ /**
+ * This install session conflicts (or is inconsistent with) with
+ * another package already installed on the device. For example, an
+ * existing permission, incompatible certificates, etc. The user may
+ * be able to uninstall another app to fix the issue.
+ */
+ public void onFailureConflict(String msg, String packageName) {
+ onFailure(msg);
+ }
+
+ /**
+ * This install session failed due to storage issues. For example,
+ * the device may be running low on space, or the required external
+ * media may be unavailable. The user may be able to help free space
+ * or insert the correct media.
+ */
+ public void onFailureStorage(String msg) {
+ onFailure(msg);
+ }
+
+ /**
+ * This install session is fundamentally incompatible with this
+ * device. For example, the package may require a hardware feature
+ * that doesn't exist, it may be missing native code for the device
+ * ABI, or it requires a newer SDK version, etc. This install would
+ * never succeed.
+ */
+ public void onFailureIncompatible(String msg) {
+ onFailure(msg);
+ }
+ }
+
+ private static class CommitResultCallbackDelegate extends PackageInstallObserver {
+ private final CommitResultCallback target;
+
+ public CommitResultCallbackDelegate(CommitResultCallback target) {
+ this.target = target;
+ }
+
+ @Override
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+ String msg) {
+ final String otherPackage = null;
+
+ switch (returnCode) {
+ case PackageManager.INSTALL_SUCCEEDED: target.onSuccess(); break;
+ case PackageManager.INSTALL_FAILED_ALREADY_EXISTS: target.onFailureConflict("INSTALL_FAILED_ALREADY_EXISTS: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_INVALID_APK: target.onFailureInvalid("INSTALL_FAILED_INVALID_APK: " + msg); break;
+ case PackageManager.INSTALL_FAILED_INVALID_URI: target.onFailureInvalid("INSTALL_FAILED_INVALID_URI: " + msg); break;
+ case PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE: target.onFailureStorage("INSTALL_FAILED_INSUFFICIENT_STORAGE: " + msg); break;
+ case PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PACKAGE: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_NO_SHARED_USER: target.onFailureConflict("INSTALL_FAILED_NO_SHARED_USER: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_UPDATE_INCOMPATIBLE: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: target.onFailureConflict("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY: target.onFailureIncompatible("INSTALL_FAILED_MISSING_SHARED_LIBRARY: " + msg); break;
+ case PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE: target.onFailureConflict("INSTALL_FAILED_REPLACE_COULDNT_DELETE: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_DEXOPT: target.onFailureInvalid("INSTALL_FAILED_DEXOPT: " + msg); break;
+ case PackageManager.INSTALL_FAILED_OLDER_SDK: target.onFailureIncompatible("INSTALL_FAILED_OLDER_SDK: " + msg); break;
+ case PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER: target.onFailureConflict("INSTALL_FAILED_CONFLICTING_PROVIDER: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_NEWER_SDK: target.onFailureIncompatible("INSTALL_FAILED_NEWER_SDK: " + msg); break;
+ case PackageManager.INSTALL_FAILED_TEST_ONLY: target.onFailureInvalid("INSTALL_FAILED_TEST_ONLY: " + msg); break;
+ case PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: target.onFailureIncompatible("INSTALL_FAILED_CPU_ABI_INCOMPATIBLE: " + msg); break;
+ case PackageManager.INSTALL_FAILED_MISSING_FEATURE: target.onFailureIncompatible("INSTALL_FAILED_MISSING_FEATURE: " + msg); break;
+ case PackageManager.INSTALL_FAILED_CONTAINER_ERROR: target.onFailureStorage("INSTALL_FAILED_CONTAINER_ERROR: " + msg); break;
+ case PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION: target.onFailureStorage("INSTALL_FAILED_INVALID_INSTALL_LOCATION: " + msg); break;
+ case PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE: target.onFailureStorage("INSTALL_FAILED_MEDIA_UNAVAILABLE: " + msg); break;
+ case PackageManager.INSTALL_FAILED_VERIFICATION_TIMEOUT: target.onFailure("INSTALL_FAILED_VERIFICATION_TIMEOUT: " + msg); break;
+ case PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE: target.onFailure("INSTALL_FAILED_VERIFICATION_FAILURE: " + msg); break;
+ case PackageManager.INSTALL_FAILED_PACKAGE_CHANGED: target.onFailureInvalid("INSTALL_FAILED_PACKAGE_CHANGED: " + msg); break;
+ case PackageManager.INSTALL_FAILED_UID_CHANGED: target.onFailureInvalid("INSTALL_FAILED_UID_CHANGED: " + msg); break;
+ case PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE: target.onFailureInvalid("INSTALL_FAILED_VERSION_DOWNGRADE: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_NOT_APK: target.onFailureInvalid("INSTALL_PARSE_FAILED_NOT_APK: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_MANIFEST: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: target.onFailureInvalid("INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_NO_CERTIFICATES: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: target.onFailureInvalid("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: target.onFailureInvalid("INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: target.onFailureInvalid("INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: " + msg); break;
+ case PackageManager.INSTALL_PARSE_FAILED_MANIFEST_EMPTY: target.onFailureInvalid("INSTALL_PARSE_FAILED_MANIFEST_EMPTY: " + msg); break;
+ case PackageManager.INSTALL_FAILED_INTERNAL_ERROR: target.onFailure("INSTALL_FAILED_INTERNAL_ERROR: " + msg); break;
+ case PackageManager.INSTALL_FAILED_USER_RESTRICTED: target.onFailureIncompatible("INSTALL_FAILED_USER_RESTRICTED: " + msg); break;
+ case PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION: target.onFailureConflict("INSTALL_FAILED_DUPLICATE_PERMISSION: " + msg, otherPackage); break;
+ case PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS: target.onFailureInvalid("INSTALL_FAILED_NO_MATCHING_ABIS: " + msg); break;
+ default: target.onFailure(msg); break;
+ }
+ }
+ }
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 625005b..03d4701 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -17,9 +17,9 @@
package android.content.pm;
import android.annotation.IntDef;
-import android.annotation.SystemApi;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.app.PackageInstallObserver;
import android.content.ComponentName;
import android.content.Context;
@@ -29,13 +29,12 @@
import android.content.pm.PackageParser.PackageParserException;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
-import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Environment;
import android.os.UserHandle;
import android.util.AndroidException;
-import android.util.DisplayMetrics;
import java.io.File;
import java.lang.annotation.Retention;
@@ -750,7 +749,7 @@
* permission that is already defined by some existing package.
*
* <p>The package name of the app which has already defined the permission is passed to
- * a {@link IPackageInstallObserver2}, if any, as the {@link #EXTRA_EXISTING_PACKAGE}
+ * a {@link PackageInstallObserver}, if any, as the {@link #EXTRA_EXISTING_PACKAGE}
* string extra; and the name of the permission being redefined is passed in the
* {@link #EXTRA_EXISTING_PERMISSION} string extra.
* @hide
@@ -841,8 +840,9 @@
public static final int DELETE_FAILED_USER_RESTRICTED = -3;
/**
- * Deletion failed return code: this is returned from the PackageInstaller
- * activity if it failed to delete a package because the a profile
+ * Deletion failed return code: this is passed to the
+ * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system
+ * failed to delete the package because a profile
* or device owner has marked the package as uninstallable.
*
* @hide
@@ -1454,6 +1454,14 @@
public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and
+ * {@link #hasSystemFeature}: This device supports HDMI-CEC.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
+
+ /**
* Action to external storage service to clean out removed apps.
* @hide
*/
@@ -1543,7 +1551,7 @@
= "android.content.pm.extra.PERMISSION_LIST";
/**
- * String extra for {@link IPackageInstallObserver2} in the 'extras' Bundle in case of
+ * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
* {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the package which provides
* the existing definition for the permission.
* @hide
@@ -1552,7 +1560,7 @@
= "android.content.pm.extra.FAILURE_EXISTING_PACKAGE";
/**
- * String extra for {@link IPackageInstallObserver2} in the 'extras' Bundle in case of
+ * String extra for {@link PackageInstallObserver} in the 'extras' Bundle in case of
* {@link #INSTALL_FAILED_DUPLICATE_PERMISSION}. This extra names the permission that is
* being redundantly defined by the package being installed.
* @hide
@@ -2940,26 +2948,29 @@
}
/**
- * @hide
- *
- * Install a package. Since this may take a little while, the result will
- * be posted back to the given observer. An installation will fail if the calling context
- * lacks the {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if the
- * package named in the package file's manifest is already installed, or if there's no space
- * available on the device.
- *
- * @param packageURI The location of the package file to install. This can be a 'file:' or a
- * 'content:' URI.
- * @param observer An observer callback to get notified when the package installation is
- * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be
- * called when that happens. This parameter must not be null.
+ * @hide Install a package. Since this may take a little while, the result
+ * will be posted back to the given observer. An installation will
+ * fail if the calling context lacks the
+ * {@link android.Manifest.permission#INSTALL_PACKAGES} permission, if
+ * the package named in the package file's manifest is already
+ * installed, or if there's no space available on the device.
+ * @param packageURI The location of the package file to install. This can
+ * be a 'file:' or a 'content:' URI.
+ * @param observer An observer callback to get notified when the package
+ * installation is complete.
+ * {@link IPackageInstallObserver#packageInstalled(String, int)}
+ * will be called when that happens. This parameter must not be
+ * null.
* @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
- * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
- * @param installerPackageName Optional package name of the application that is performing the
- * installation. This identifies which market the package came from.
- * @deprecated Use {@link #installPackage(Uri, IPackageInstallObserver2, int, String)}
- * instead. This method will continue to be supported but the older observer interface
- * will not get additional failure details.
+ * {@link #INSTALL_REPLACE_EXISTING},
+ * {@link #INSTALL_ALLOW_TEST}.
+ * @param installerPackageName Optional package name of the application that
+ * is performing the installation. This identifies which market
+ * the package came from.
+ * @deprecated Use {@link #installPackage(Uri, PackageInstallObserver, int,
+ * String)} instead. This method will continue to be supported
+ * but the older observer interface will not get additional
+ * failure details.
*/
// @SystemApi
public abstract void installPackage(
@@ -2976,9 +2987,11 @@
* @param observer An observer callback to get notified when the package
* installation is complete.
* {@link IPackageInstallObserver#packageInstalled(String, int)}
- * will be called when that happens. This parameter must not be null.
+ * will be called when that happens. This parameter must not be
+ * null.
* @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
- * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
+ * {@link #INSTALL_REPLACE_EXISTING},
+ * {@link #INSTALL_ALLOW_TEST}.
* @param installerPackageName Optional package name of the application that
* is performing the installation. This identifies which market
* the package came from.
@@ -2991,10 +3004,11 @@
* these parameters describing the encryption and authentication
* used. May be {@code null}.
* @hide
- * @deprecated Use {@link #installPackageWithVerification(Uri, IPackageInstallObserver2,
- * int, String, Uri, ManifestDigest, ContainerEncryptionParams)} instead. This method will
- * continue to be supported but the older observer interface will not get additional failure
- * details.
+ * @deprecated Use {@link #installPackageWithVerification(Uri,
+ * PackageInstallObserver, int, String, Uri, ManifestDigest,
+ * ContainerEncryptionParams)} instead. This method will
+ * continue to be supported but the older observer interface
+ * will not get additional failure details.
*/
// @SystemApi
public abstract void installPackageWithVerification(Uri packageURI,
@@ -3012,9 +3026,11 @@
* @param observer An observer callback to get notified when the package
* installation is complete.
* {@link IPackageInstallObserver#packageInstalled(String, int)}
- * will be called when that happens. This parameter must not be null.
+ * will be called when that happens. This parameter must not be
+ * null.
* @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
- * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
+ * {@link #INSTALL_REPLACE_EXISTING},
+ * {@link #INSTALL_ALLOW_TEST}.
* @param installerPackageName Optional package name of the application that
* is performing the installation. This identifies which market
* the package came from.
@@ -3023,12 +3039,12 @@
* @param encryptionParams if the package to be installed is encrypted,
* these parameters describing the encryption and authentication
* used. May be {@code null}.
- *
* @hide
* @deprecated Use {@link #installPackageWithVerificationAndEncryption(Uri,
- * IPackageInstallObserver2, int, String, VerificationParams,
- * ContainerEncryptionParams)} instead. This method will continue to be
- * supported but the older observer interface will not get additional failure details.
+ * PackageInstallObserver, int, String, VerificationParams,
+ * ContainerEncryptionParams)} instead. This method will
+ * continue to be supported but the older observer interface
+ * will not get additional failure details.
*/
@Deprecated
public abstract void installPackageWithVerificationAndEncryption(Uri packageURI,
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0b6a926..d92a90d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -73,11 +73,9 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.StrictJarFile;
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index d505365..519bbb6 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -84,10 +84,11 @@
* TODO: Remove these constants and strip out any code that previously relied on them
* being set to true.
*/
- static final boolean LIE_ABOUT_AE_STATE = true;
- static final boolean LIE_ABOUT_AE_MAX_REGIONS = true;
+ static final boolean LIE_ABOUT_AE_STATE = false;
+ static final boolean LIE_ABOUT_AE_MAX_REGIONS = false;
static final boolean LIE_ABOUT_AF = true;
static final boolean LIE_ABOUT_AF_MAX_REGIONS = true;
+ static final boolean LIE_ABOUT_AWB_STATE = false;
static final boolean LIE_ABOUT_AWB = true;
/**
@@ -201,7 +202,7 @@
/*
* info.supportedHardwareLevel
*/
- m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED);
+ m.set(INFO_SUPPORTED_HARDWARE_LEVEL, INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
/*
* scaler.availableStream*, scaler.available*Durations, sensor.info.maxFrameDuration
@@ -330,16 +331,17 @@
List<String> flashModes = p.getSupportedFlashModes();
String[] flashModeStrings = new String[] {
+ Camera.Parameters.FLASH_MODE_OFF,
Camera.Parameters.FLASH_MODE_AUTO,
Camera.Parameters.FLASH_MODE_ON,
Camera.Parameters.FLASH_MODE_RED_EYE,
// Map these manually
Camera.Parameters.FLASH_MODE_TORCH,
- Camera.Parameters.FLASH_MODE_OFF,
};
int[] flashModeInts = new int[] {
CONTROL_AE_MODE_ON,
CONTROL_AE_MODE_ON_AUTO_FLASH,
+ CONTROL_AE_MODE_ON_ALWAYS_FLASH,
CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE
};
int[] aeAvail = ArrayUtils.convertStringListToIntArray(
@@ -355,6 +357,25 @@
// Note that AE_MODE_OFF is never available.
m.set(CONTROL_AE_AVAILABLE_MODES, aeAvail);
}
+
+ /*
+ * control.aeCompensationRanges
+ */
+ {
+ int min = p.getMinExposureCompensation();
+ int max = p.getMaxExposureCompensation();
+
+ m.set(CONTROL_AE_COMPENSATION_RANGE, Range.create(min, max));
+ }
+
+ /*
+ * control.aeCompensationStep
+ */
+ {
+ float step = p.getExposureCompensationStep();
+
+ m.set(CONTROL_AE_COMPENSATION_STEP, ParamsUtils.createRational(step));
+ }
}
private static void mapControlAwb(CameraMetadataNative m, Camera.Parameters p) {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
index 17dda08..4a9afa6 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyRequestMapper.java
@@ -30,6 +30,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import static com.android.internal.util.Preconditions.*;
import static android.hardware.camera2.CaptureRequest.*;
@@ -41,12 +42,6 @@
private static final String TAG = "LegacyRequestMapper";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
- /** The default normalized camera area spans the entire size of the preview viewport */
- private static final Camera.Area CAMERA_AREA_DEFAULT =
- new Camera.Area(
- new Rect(/*left*/-1000, /*top*/-1000, /*right*/1000, /*bottom*/1000),
- /*weight*/1);
-
/**
* Set the legacy parameters using the {@link LegacyRequest legacy request}.
*
@@ -61,40 +56,20 @@
Size previewSize = legacyRequest.previewSize;
Camera.Parameters params = legacyRequest.parameters;
+ Rect activeArray = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+
/*
* scaler.cropRegion
*/
+ ParameterUtils.ZoomData zoomData;
{
- Rect activeArraySize = characteristics.get(
- CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
- Rect activeArraySizeOnly = new Rect(
- /*left*/0, /*top*/0,
- activeArraySize.width(), activeArraySize.height());
+ zoomData = ParameterUtils.convertScalerCropRegion(activeArray,
+ request.get(SCALER_CROP_REGION),
+ previewSize,
+ params);
- Rect userCropRegion = request.get(SCALER_CROP_REGION);
-
- if (userCropRegion == null) {
- userCropRegion = activeArraySizeOnly;
- }
-
- if (VERBOSE) {
- Log.v(TAG, "convertRequestToMetadata - user crop region was " + userCropRegion);
- }
-
- Rect reportedCropRegion = new Rect();
- Rect previewCropRegion = new Rect();
- int zoomIndex = ParameterUtils.getClosestAvailableZoomCrop(params, activeArraySizeOnly,
- previewSize, userCropRegion,
- /*out*/reportedCropRegion, /*out*/previewCropRegion);
-
- if (VERBOSE) {
- Log.v(TAG, "convertRequestToMetadata - zoom calculated to: " +
- "zoomIndex = " + zoomIndex +
- ", reported crop region = " + reportedCropRegion +
- ", preview crop region = " + previewCropRegion);
- }
if (params.isZoomSupported()) {
- params.setZoom(zoomIndex);
+ params.setZoom(zoomData.zoomIndex);
} else if (VERBOSE) {
Log.v(TAG, "convertRequestToMetadata - zoom is not supported");
}
@@ -126,49 +101,29 @@
}
/*
- * control.aeRegions
- * -- ORDER OF EXECUTION MATTERS:
- * -- This must be done after the crop region (zoom) was already set in the parameters
+ * control.aeRegions, afRegions
*/
{
- MeteringRectangle[] aeRegions = request.get(CONTROL_AE_REGIONS);
- int maxNumMeteringAreas = params.getMaxNumMeteringAreas();
- if (aeRegions != null && maxNumMeteringAreas > 0) {
- // Add all non-zero weight regions to the list
- List<MeteringRectangle> meteringRectangleList = new ArrayList<>();
- for (MeteringRectangle rect : aeRegions) {
- if (rect.getMeteringWeight() != MeteringRectangle.METERING_WEIGHT_DONT_CARE) {
- meteringRectangleList.add(rect);
- }
- }
-
- // Ignore any regions beyond our maximum supported count
- int countMeteringAreas =
- Math.min(maxNumMeteringAreas, meteringRectangleList.size());
- List<Camera.Area> meteringAreaList = new ArrayList<>(countMeteringAreas);
- Rect activeArray = characteristics.get(
- CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
-
- for (int i = 0; i < countMeteringAreas; ++i) {
- MeteringRectangle rect = meteringRectangleList.get(i);
-
- Camera.Area area = convertMeteringRectangleToLegacy(activeArray, rect);
- meteringAreaList.add(area);
- }
+ // aeRegions
+ {
+ MeteringRectangle[] aeRegions = request.get(CONTROL_AE_REGIONS);
+ int maxNumMeteringAreas = params.getMaxNumMeteringAreas();
+ List<Camera.Area> meteringAreaList = convertMeteringRegionsToLegacy(
+ activeArray, zoomData, aeRegions, maxNumMeteringAreas,
+ /*regionName*/"AE");
params.setMeteringAreas(meteringAreaList);
+ }
- if (maxNumMeteringAreas < meteringRectangleList.size()) {
- Log.w(TAG,
- "convertRequestToMetadata - Too many requested AE regions, "
- + "ignoring all beyond the first " + maxNumMeteringAreas);
- }
- } else {
- if (maxNumMeteringAreas > 0) {
- params.setMeteringAreas(Arrays.asList(CAMERA_AREA_DEFAULT));
- } else {
- params.setMeteringAreas(null);
- }
+ // afRegions
+ {
+ MeteringRectangle[] afRegions = request.get(CONTROL_AF_REGIONS);
+ int maxNumFocusAreas = params.getMaxNumFocusAreas();
+ List<Camera.Area> focusAreaList = convertMeteringRegionsToLegacy(
+ activeArray, zoomData, afRegions, maxNumFocusAreas,
+ /*regionName*/"AF");
+
+ params.setFocusAreas(focusAreaList);
}
}
@@ -199,13 +154,102 @@
* control
*/
+ // control.aeExposureCompensation
+ {
+ Range<Integer> compensationRange =
+ characteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
+ int compensation = getOrDefault(request,
+ CONTROL_AE_EXPOSURE_COMPENSATION,
+ /*defaultValue*/0);
+
+ if (!compensationRange.contains(compensation)) {
+ Log.w(TAG,
+ "convertRequestMetadata - control.aeExposureCompensation " +
+ "is out of range, ignoring value");
+ compensation = 0;
+ }
+
+ params.setExposureCompensation(compensation);
+ }
+
+ // control.aeLock
+ {
+ Boolean aeLock = getIfSupported(request, CONTROL_AE_LOCK, /*defaultValue*/false,
+ params.isAutoExposureLockSupported(),
+ /*allowedValue*/false);
+
+ if (aeLock != null) {
+ params.setAutoExposureLock(aeLock);
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, "convertRequestToMetadata - control.aeLock set to " + aeLock);
+ }
+
+ // TODO: Don't add control.aeLock to availableRequestKeys if it's not supported
+ }
+
// control.aeMode, flash.mode
mapAeAndFlashMode(request, /*out*/params);
// control.awbLock
- Boolean awbLock = request.get(CONTROL_AWB_LOCK);
- params.setAutoWhiteBalanceLock(awbLock == null ? false : awbLock);
+ {
+ Boolean awbLock = getIfSupported(request, CONTROL_AWB_LOCK, /*defaultValue*/false,
+ params.isAutoWhiteBalanceLockSupported(),
+ /*allowedValue*/false);
+ if (awbLock != null) {
+ params.setAutoWhiteBalanceLock(awbLock);
+ }
+
+ // TODO: Don't add control.awbLock to availableRequestKeys if it's not supported
+ }
+ }
+
+ private static List<Camera.Area> convertMeteringRegionsToLegacy(
+ Rect activeArray, ParameterUtils.ZoomData zoomData,
+ MeteringRectangle[] meteringRegions, int maxNumMeteringAreas, String regionName) {
+ if (meteringRegions == null || maxNumMeteringAreas <= 0) {
+ if (maxNumMeteringAreas > 0) {
+ return Arrays.asList(ParameterUtils.CAMERA_AREA_DEFAULT);
+ } else {
+ return null;
+ }
+ }
+
+ // Add all non-zero weight regions to the list
+ List<MeteringRectangle> meteringRectangleList = new ArrayList<>();
+ for (MeteringRectangle rect : meteringRegions) {
+ if (rect.getMeteringWeight() != MeteringRectangle.METERING_WEIGHT_DONT_CARE) {
+ meteringRectangleList.add(rect);
+ }
+ }
+
+ // Ignore any regions beyond our maximum supported count
+ int countMeteringAreas =
+ Math.min(maxNumMeteringAreas, meteringRectangleList.size());
+ List<Camera.Area> meteringAreaList = new ArrayList<>(countMeteringAreas);
+
+ for (int i = 0; i < countMeteringAreas; ++i) {
+ MeteringRectangle rect = meteringRectangleList.get(i);
+
+ ParameterUtils.MeteringData meteringData =
+ ParameterUtils.convertMeteringRectangleToLegacy(activeArray, rect, zoomData);
+ meteringAreaList.add(meteringData.meteringArea);
+ }
+
+ if (maxNumMeteringAreas < meteringRectangleList.size()) {
+ Log.w(TAG,
+ "convertMeteringRegionsToLegacy - Too many requested " + regionName +
+ " regions, ignoring all beyond the first " + maxNumMeteringAreas);
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, "convertMeteringRegionsToLegacy - " + regionName + " areas = "
+ + ParameterUtils.stringFromAreaList(meteringAreaList));
+ }
+
+ return meteringAreaList;
}
private static void mapAeAndFlashMode(CaptureRequest r, /*out*/Parameters p) {
@@ -214,34 +258,70 @@
List<String> supportedFlashModes = p.getSupportedFlashModes();
+ String flashModeSetting = null;
+
+ // Flash is OFF by default, on cameras that support flash
+ if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_OFF)) {
+ flashModeSetting = Parameters.FLASH_MODE_OFF;
+ }
+
/*
* Map all of the control.aeMode* enums, but ignore AE_MODE_OFF since we never support it
*/
// Ignore flash.mode controls unless aeMode == ON
if (aeMode == CONTROL_AE_MODE_ON) {
- // Flash is OFF by default
- p.setFlashMode(Parameters.FLASH_MODE_OFF);
-
- if (flashMode == FLASH_MODE_TORCH &&
- ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_TORCH)) {
- p.setFlashMode(Parameters.FLASH_MODE_TORCH);
- } else if (flashMode == FLASH_MODE_SINGLE &&
- ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
- p.setFlashMode(Parameters.FLASH_MODE_ON);
+ if (flashMode == FLASH_MODE_TORCH) {
+ if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_TORCH)) {
+ flashModeSetting = Parameters.FLASH_MODE_TORCH;
+ } else {
+ Log.w(TAG, "mapAeAndFlashMode - Ignore flash.mode == TORCH;" +
+ "camera does not support it");
+ }
+ } else if (flashMode == FLASH_MODE_SINGLE) {
+ if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
+ flashModeSetting = Parameters.FLASH_MODE_ON;
+ } else {
+ Log.w(TAG, "mapAeAndFlashMode - Ignore flash.mode == SINGLE;" +
+ "camera does not support it");
+ }
+ } else {
+ // Use the default FLASH_MODE_OFF
}
- } else if (aeMode == CONTROL_AE_MODE_ON_ALWAYS_FLASH &&
- ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
- p.setFlashMode(Parameters.FLASH_MODE_ON);
- } else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH &&
- ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_AUTO)) {
- p.setFlashMode(Parameters.FLASH_MODE_AUTO);
- } else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE &&
- ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_RED_EYE)) {
- p.setFlashMode(Parameters.FLASH_MODE_RED_EYE);
+ } else if (aeMode == CONTROL_AE_MODE_ON_ALWAYS_FLASH) {
+ if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_ON)) {
+ flashModeSetting = Parameters.FLASH_MODE_ON;
+ } else {
+ Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_ALWAYS_FLASH;" +
+ "camera does not support it");
+ }
+ } else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH) {
+ if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_AUTO)) {
+ flashModeSetting = Parameters.FLASH_MODE_AUTO;
+ } else {
+ Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_AUTO_FLASH;" +
+ "camera does not support it");
+ }
+ } else if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE) {
+ if (ListUtils.listContains(supportedFlashModes, Parameters.FLASH_MODE_RED_EYE)) {
+ flashModeSetting = Parameters.FLASH_MODE_RED_EYE;
+ } else {
+ Log.w(TAG, "mapAeAndFlashMode - Ignore control.aeMode == ON_AUTO_FLASH_REDEYE;"
+ + "camera does not support it");
+ }
} else {
// Default to aeMode == ON, flash = OFF
- p.setFlashMode(Parameters.FLASH_MODE_OFF);
+ }
+
+ if (flashModeSetting != null) {
+ p.setFlashMode(flashModeSetting);
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG,
+ "mapAeAndFlashMode - set flash.mode (api1) to " + flashModeSetting
+ + ", requested (api2) " + flashMode
+ + ", supported (api1) " + ListUtils.listToString(supportedFlashModes));
}
}
@@ -275,21 +355,7 @@
return legacyFps;
}
- private static Camera.Area convertMeteringRectangleToLegacy(
- Rect activeArray, MeteringRectangle meteringRect) {
- // TODO: use matrix transform magic here
-
- Rect rect = new Rect();
-
- // TODO: Take the cropRegion (zooming) into account here
-
- // TODO: crop to be within [-1000, 1000] range for both X and Y if the values end up too big
- //return new Camera.Area(rect, meteringRect.getMeteringWeight());
-
- Log.w(TAG, "convertMeteringRectangleToLegacy - TODO: support metering rects");
- return CAMERA_AREA_DEFAULT;
- }
-
+ /** Return the value set by the key, or the {@code defaultValue} if no value was set. */
private static <T> T getOrDefault(CaptureRequest r, CaptureRequest.Key<T> key, T defaultValue) {
checkNotNull(r, "r must not be null");
checkNotNull(key, "key must not be null");
@@ -302,4 +368,29 @@
return value;
}
}
+
+ /**
+ * Return {@code null} if the value is not supported, otherwise return the retrieved key's
+ * value from the request (or the default value if it wasn't set).
+ *
+ * <p>If the fetched value in the request is equivalent to {@code allowedValue},
+ * then omit the warning (e.g. turning off AF lock on a camera
+ * that always has the AF lock turned off is a silent no-op), but still return {@code null}.</p>
+ *
+ * <p>Logs a warning to logcat if the key is not supported by api1 camera device.</p.
+ */
+ private static <T> T getIfSupported(
+ CaptureRequest r, CaptureRequest.Key<T> key, T defaultValue, boolean isSupported,
+ T allowedValue) {
+ T val = getOrDefault(r, key, defaultValue);
+
+ if (!isSupported) {
+ if (!Objects.equals(val, allowedValue)) {
+ Log.w(TAG, key.getName() + " is not supported; ignoring requested value " + val);
+ }
+ return null;
+ }
+
+ return val;
+ }
}
diff --git a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
index 79287c13..a10a2af 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyResultMapper.java
@@ -24,9 +24,18 @@
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.legacy.ParameterUtils.WeightedRectangle;
+import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.utils.ListUtils;
+import android.hardware.camera2.utils.ParamsUtils;
import android.util.Log;
+import android.util.Rational;
import android.util.Size;
+import java.util.ArrayList;
+import java.util.List;
+
import static com.android.internal.util.Preconditions.*;
import static android.hardware.camera2.CaptureResult.*;
@@ -54,6 +63,11 @@
CameraMetadataNative result = new CameraMetadataNative();
+ Rect activeArraySize = characteristics.get(
+ CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+ ZoomData zoomData = ParameterUtils.convertScalerCropRegion(activeArraySize,
+ request.get(CaptureRequest.SCALER_CROP_REGION), previewSize, params);
+
/*
* control
*/
@@ -66,24 +80,34 @@
/*
* control.ae*
*/
- mapAe(result, /*out*/params);
+ mapAe(result, characteristics, request, activeArraySize, zoomData, /*out*/params);
// control.awbLock
result.set(CaptureResult.CONTROL_AWB_LOCK, params.getAutoWhiteBalanceLock());
// control.awbState
- if (LegacyMetadataMapper.LIE_ABOUT_AWB) {
+ if (LegacyMetadataMapper.LIE_ABOUT_AWB_STATE) {
// Lie to pass CTS temporarily.
// TODO: CTS needs to be updated not to query this value
// for LIMITED devices unless its guaranteed to be available.
result.set(CaptureResult.CONTROL_AWB_STATE,
CameraMetadata.CONTROL_AWB_STATE_CONVERGED);
// TODO: Read the awb mode from parameters instead
+ }
+
+ if (LegacyMetadataMapper.LIE_ABOUT_AWB) {
result.set(CaptureResult.CONTROL_AWB_MODE,
request.get(CaptureRequest.CONTROL_AWB_MODE));
}
/*
+ * flash
+ */
+ {
+ // TODO
+ }
+
+ /*
* lens
*/
// lens.focalLength
@@ -92,7 +116,7 @@
/*
* scaler
*/
- mapScaler(result, characteristics, request, previewSize, params);
+ mapScaler(result, zoomData, /*out*/params);
/*
* sensor
@@ -104,7 +128,9 @@
return result;
}
- private static void mapAe(CameraMetadataNative m, /*out*/Parameters p) {
+ private static void mapAe(CameraMetadataNative m,
+ CameraCharacteristics characteristics,
+ CaptureRequest request, Rect activeArray, ZoomData zoomData, /*out*/Parameters p) {
// control.aeAntiBandingMode
{
int antiBandingMode = LegacyMetadataMapper.convertAntiBandingModeOrDefault(
@@ -112,8 +138,31 @@
m.set(CONTROL_AE_ANTIBANDING_MODE, antiBandingMode);
}
- // control.aeMode, flash.mode
- mapAeAndFlashMode(m, p);
+ // control.aeExposureCompensation
+ {
+ m.set(CONTROL_AE_EXPOSURE_COMPENSATION, p.getExposureCompensation());
+ }
+
+ // control.aeLock
+ {
+ boolean lock = p.isAutoExposureLockSupported() ? p.getAutoExposureLock() : false;
+ m.set(CONTROL_AE_LOCK, lock);
+ if (VERBOSE) {
+ Log.v(TAG,
+ "mapAe - android.control.aeLock = " + lock +
+ ", supported = " + p.isAutoExposureLockSupported());
+ }
+
+ Boolean requestLock = request.get(CaptureRequest.CONTROL_AE_LOCK);
+ if (requestLock != null && requestLock != lock) {
+ Log.w(TAG,
+ "mapAe - android.control.aeLock was requested to " + requestLock +
+ " but resulted in " + lock);
+ }
+ }
+
+ // control.aeMode, flash.mode, flash.state
+ mapAeAndFlashMode(m, characteristics, p);
// control.aeState
if (LegacyMetadataMapper.LIE_ABOUT_AE_STATE) {
@@ -123,44 +172,102 @@
}
// control.aeRegions
+ {
+ if (VERBOSE) {
+ String meteringAreas = p.get("metering-areas");
+ Log.v(TAG, "mapAe - parameter dump; metering-areas: " + meteringAreas);
+ }
- /*
- * TODO: Use the *resulting* crop region to calculate intersection with
- * metering region
- *
- * Report the sensor-relative metering region in the result even
- * if that's not actually the real thing (similar to how we do it
- * for zooming)
- */
- }
+ MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
+ zoomData, p.getMeteringAreas(), "AE");
-
- /** Map results for control.aeMode, flash.mode */
- private static void mapAeAndFlashMode(CameraMetadataNative m, /*out*/Parameters p) {
- // Default: AE mode on but flash never fires
- int flashMode = FLASH_MODE_OFF;
- int aeMode = CONTROL_AE_MODE_ON;
-
- switch (p.getFlashMode()) {
- case Parameters.FLASH_MODE_OFF:
- break; // ok, using default
- case Parameters.FLASH_MODE_AUTO:
- aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
- break;
- case Parameters.FLASH_MODE_ON:
- // flashMode = SINGLE + aeMode = ON is indistinguishable from ON_ALWAYS_FLASH
- aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
- break;
- case Parameters.FLASH_MODE_RED_EYE:
- aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
- break;
- case Parameters.FLASH_MODE_TORCH:
- flashMode = FLASH_MODE_TORCH;
- break;
- default:
- Log.w(TAG, "mapAeAndFlashMode - Ignoring unknown flash mode " + p.getFlashMode());
+ m.set(CONTROL_AE_REGIONS, meteringRectArray);
}
+ // control.afRegions
+ {
+ if (VERBOSE) {
+ String focusAreas = p.get("focus-areas");
+ Log.v(TAG, "mapAe - parameter dump; focus-areas: " + focusAreas);
+ }
+
+ MeteringRectangle[] meteringRectArray = getMeteringRectangles(activeArray,
+ zoomData, p.getFocusAreas(), "AF");
+
+ m.set(CONTROL_AF_REGIONS, meteringRectArray);
+ }
+
+ // control.awbLock
+ {
+ boolean lock = p.isAutoWhiteBalanceLockSupported() ?
+ p.getAutoWhiteBalanceLock() : false;
+ m.set(CONTROL_AWB_LOCK, lock);
+ }
+ }
+
+ private static MeteringRectangle[] getMeteringRectangles(Rect activeArray, ZoomData zoomData,
+ List<Camera.Area> meteringAreaList, String regionName) {
+ List<MeteringRectangle> meteringRectList = new ArrayList<>();
+ if (meteringAreaList != null) {
+ for (Camera.Area area : meteringAreaList) {
+ WeightedRectangle rect =
+ ParameterUtils.convertCameraAreaToActiveArrayRectangle(
+ activeArray, zoomData, area);
+
+ meteringRectList.add(rect.toMetering());
+ }
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG,
+ "Metering rectangles for " + regionName + ": "
+ + ListUtils.listToString(meteringRectList));
+ }
+
+ return meteringRectList.toArray(new MeteringRectangle[0]);
+ }
+
+ /** Map results for control.aeMode, flash.mode, flash.state */
+ private static void mapAeAndFlashMode(CameraMetadataNative m,
+ CameraCharacteristics characteristics, Parameters p) {
+ // Default: AE mode on but flash never fires
+ int flashMode = FLASH_MODE_OFF;
+ // If there is no flash on this camera, the state is always unavailable
+ // , otherwise it's only known for TORCH/SINGLE modes
+ Integer flashState = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)
+ ? null : FLASH_STATE_UNAVAILABLE;
+ int aeMode = CONTROL_AE_MODE_ON;
+
+ String flashModeSetting = p.getFlashMode();
+
+ if (flashModeSetting != null) {
+ switch (flashModeSetting) {
+ case Parameters.FLASH_MODE_OFF:
+ break; // ok, using default
+ case Parameters.FLASH_MODE_AUTO:
+ aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH;
+ break;
+ case Parameters.FLASH_MODE_ON:
+ // flashMode = SINGLE + aeMode = ON is indistinguishable from ON_ALWAYS_FLASH
+ flashMode = FLASH_MODE_SINGLE;
+ aeMode = CONTROL_AE_MODE_ON_ALWAYS_FLASH;
+ flashState = FLASH_STATE_FIRED;
+ break;
+ case Parameters.FLASH_MODE_RED_EYE:
+ aeMode = CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE;
+ break;
+ case Parameters.FLASH_MODE_TORCH:
+ flashMode = FLASH_MODE_TORCH;
+ flashState = FLASH_STATE_FIRED;
+ break;
+ default:
+ Log.w(TAG,
+ "mapAeAndFlashMode - Ignoring unknown flash mode " + p.getFlashMode());
+ }
+ }
+
+ // flash.state
+ m.set(FLASH_STATE, flashState);
// flash.mode
m.set(FLASH_MODE, flashMode);
// control.aeMode
@@ -169,33 +276,13 @@
/** Map results for scaler.* */
private static void mapScaler(CameraMetadataNative m,
- CameraCharacteristics characteristics,
- CaptureRequest request,
- Size previewSize,
+ ZoomData zoomData,
/*out*/Parameters p) {
/*
* scaler.cropRegion
*/
{
- Rect activeArraySize = characteristics.get(
- CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
- Rect activeArraySizeOnly = new Rect(
- /*left*/0, /*top*/0,
- activeArraySize.width(), activeArraySize.height());
-
- Rect userCropRegion = request.get(CaptureRequest.SCALER_CROP_REGION);
-
- if (userCropRegion == null) {
- userCropRegion = activeArraySizeOnly;
- }
-
- Rect reportedCropRegion = new Rect();
- Rect previewCropRegion = new Rect();
- ParameterUtils.getClosestAvailableZoomCrop(p, activeArraySizeOnly,
- previewSize, userCropRegion,
- /*out*/reportedCropRegion, /*out*/previewCropRegion);
-
- m.set(SCALER_CROP_REGION, reportedCropRegion);
+ m.set(SCALER_CROP_REGION, zoomData.reportedCrop);
}
}
}
diff --git a/core/java/android/hardware/camera2/legacy/ParameterUtils.java b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
index c3b1bbd..efd12f2 100644
--- a/core/java/android/hardware/camera2/legacy/ParameterUtils.java
+++ b/core/java/android/hardware/camera2/legacy/ParameterUtils.java
@@ -17,9 +17,15 @@
package android.hardware.camera2.legacy;
import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.Camera;
+import android.hardware.Camera.Area;
+import android.hardware.camera2.legacy.ParameterUtils.MeteringData;
+import android.hardware.camera2.legacy.ParameterUtils.ZoomData;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.MeteringRectangle;
import android.hardware.camera2.utils.ListUtils;
import android.hardware.camera2.utils.ParamsUtils;
import android.hardware.camera2.utils.SizeAreaComparator;
@@ -38,6 +44,181 @@
* Various utilities for dealing with camera API1 parameters.
*/
public class ParameterUtils {
+ /** Upper/left minimal point of a normalized rectangle */
+ public static final int NORMALIZED_RECTANGLE_MIN = -1000;
+ /** Lower/right maximal point of a normalized rectangle */
+ public static final int NORMALIZED_RECTANGLE_MAX = 1000;
+ /** The default normalized rectangle spans the entire size of the preview viewport */
+ public static final Rect NORMALIZED_RECTANGLE_DEFAULT = new Rect(
+ NORMALIZED_RECTANGLE_MIN,
+ NORMALIZED_RECTANGLE_MIN,
+ NORMALIZED_RECTANGLE_MAX,
+ NORMALIZED_RECTANGLE_MAX);
+ /** The default normalized area uses the default normalized rectangle with a weight=1 */
+ public static final Camera.Area CAMERA_AREA_DEFAULT =
+ new Camera.Area(new Rect(NORMALIZED_RECTANGLE_DEFAULT),
+ /*weight*/1);
+ /** Empty rectangle {@code 0x0+0,0} */
+ public static final Rect RECTANGLE_EMPTY =
+ new Rect(/*left*/0, /*top*/0, /*right*/0, /*bottom*/0);
+
+ /**
+ * Calculate effective/reported zoom data from a user-specified crop region.
+ */
+ public static class ZoomData {
+ /** Zoom index used by {@link Camera.Parameters#setZoom} */
+ public final int zoomIndex;
+ /** Effective crop-region given the zoom index, coordinates relative to active-array */
+ public final Rect previewCrop;
+ /** Reported crop-region given the zoom index, coordinates relative to active-array */
+ public final Rect reportedCrop;
+
+ public ZoomData(int zoomIndex, Rect previewCrop, Rect reportedCrop) {
+ this.zoomIndex = zoomIndex;
+ this.previewCrop = previewCrop;
+ this.reportedCrop = reportedCrop;
+ }
+ }
+
+ /**
+ * Calculate effective/reported metering data from a user-specified metering region.
+ */
+ public static class MeteringData {
+ /**
+ * The metering area scaled to the range of [-1000, 1000].
+ * <p>Values outside of this range are clipped to be within the range.</p>
+ */
+ public final Camera.Area meteringArea;
+ /**
+ * Effective preview metering region, coordinates relative to active-array.
+ *
+ * <p>Clipped to fit inside of the (effective) preview crop region.</p>
+ */
+ public final Rect previewMetering;
+ /**
+ * Reported metering region, coordinates relative to active-array.
+ *
+ * <p>Clipped to fit inside of the (reported) resulting crop region.</p>
+ */
+ public final Rect reportedMetering;
+
+ public MeteringData(Area meteringArea, Rect previewMetering, Rect reportedMetering) {
+ this.meteringArea = meteringArea;
+ this.previewMetering = previewMetering;
+ this.reportedMetering = reportedMetering;
+ }
+ }
+
+ /**
+ * A weighted rectangle is an arbitrary rectangle (the coordinate system is unknown) with an
+ * arbitrary weight.
+ *
+ * <p>The user of this class must know what the coordinate system ahead of time; it's
+ * then possible to convert to a more concrete type such as a metering rectangle or a face.
+ * </p>
+ *
+ * <p>When converting to a more concrete type, out-of-range values are clipped; this prevents
+ * possible illegal argument exceptions being thrown at runtime.</p>
+ */
+ public static class WeightedRectangle {
+ /** Arbitrary rectangle (the range is user-defined); never {@code null}. */
+ public final Rect rect;
+ /** Arbitrary weight (the range is user-defined). */
+ public final int weight;
+
+ /**
+ * Create a new weighted-rectangle from a non-{@code null} rectangle; the {@code weight}
+ * can be unbounded.
+ */
+ public WeightedRectangle(Rect rect, int weight) {
+ this.rect = checkNotNull(rect, "rect must not be null");
+ this.weight = weight;
+ }
+
+ /**
+ * Convert to a metering rectangle, clipping any of the values to stay within range.
+ *
+ * <p>If values are clipped, a warning is printed to logcat.</p>
+ *
+ * @return a new metering rectangle
+ */
+ public MeteringRectangle toMetering() {
+ int weight = clip(this.weight,
+ MeteringRectangle.METERING_WEIGHT_MIN,
+ MeteringRectangle.METERING_WEIGHT_MAX,
+ rect,
+ "weight");
+
+ int x = clipLower(rect.left, /*lo*/0, rect, "left");
+ int y = clipLower(rect.top, /*lo*/0, rect, "top");
+ int w = clipLower(rect.width(), /*lo*/0, rect, "width");
+ int h = clipLower(rect.height(), /*lo*/0, rect, "height");
+
+ return new MeteringRectangle(x, y, w, h, weight);
+ }
+
+ /**
+ * Convert to a face; the rect is considered to be the bounds, and the weight
+ * is considered to be the score.
+ *
+ * <p>If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX},
+ * the score is clipped first and a warning is printed to logcat.</p>
+ *
+ * <p>All other parameters are passed-through as-is.</p>
+ *
+ * @return a new face with the optional features set
+ */
+ public Face toFace(
+ int id, Point leftEyePosition, Point rightEyePosition, Point mouthPosition) {
+ int score = clip(weight,
+ Face.SCORE_MIN,
+ Face.SCORE_MAX,
+ rect,
+ "score");
+
+ return new Face(rect, score, id, leftEyePosition, rightEyePosition, mouthPosition);
+ }
+
+ /**
+ * Convert to a face; the rect is considered to be the bounds, and the weight
+ * is considered to be the score.
+ *
+ * <p>If the score is out of range of {@value Face#SCORE_MIN}, {@value Face#SCORE_MAX},
+ * the score is clipped first and a warning is printed to logcat.</p>
+ *
+ * <p>All other parameters are passed-through as-is.</p>
+ *
+ * @return a new face without the optional features
+ */
+ public Face toFace() {
+ int score = clip(weight,
+ Face.SCORE_MIN,
+ Face.SCORE_MAX,
+ rect,
+ "score");
+
+ return new Face(rect, score);
+ }
+
+ private static int clipLower(int value, int lo, Rect rect, String name) {
+ return clip(value, lo, /*hi*/Integer.MAX_VALUE, rect, name);
+ }
+
+ private static int clip(int value, int lo, int hi, Rect rect, String name) {
+ if (value < lo) {
+ Log.w(TAG, "toMetering - Rectangle " + rect + " "
+ + name + " too small, clip to " + lo);
+ value = lo;
+ } else if (value > hi) {
+ Log.w(TAG, "toMetering - Rectangle " + rect + " "
+ + name + " too small, clip to " + hi);
+ value = hi;
+ }
+
+ return value;
+ }
+ }
+
private static final String TAG = "ParameterUtils";
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -100,6 +281,35 @@
}
/**
+ * Convert a camera area list into a human-readable string
+ * @param areaList a list of areas (null is ok)
+ */
+ public static String stringFromAreaList(List<Camera.Area> areaList) {
+ StringBuilder sb = new StringBuilder();
+
+ if (areaList == null) {
+ return null;
+ }
+
+ int i = 0;
+ for (Camera.Area area : areaList) {
+ if (area == null) {
+ sb.append("null");
+ } else {
+ sb.append(stringFromArea(area));
+ }
+
+ if (i != areaList.size() - 1) {
+ sb.append(", ");
+ }
+
+ i++;
+ }
+
+ return sb.toString();
+ }
+
+ /**
* Calculate the closest zoom index for the user-requested crop region by rounding
* up to the closest (largest or equal) possible zoom crop.
*
@@ -487,6 +697,217 @@
return new SizeF(zoomRatioWidth, zoomRatioHeight);
}
+ /**
+ * Convert the user-specified crop region into zoom data; which can be used
+ * to set the parameters to a specific zoom index, or to report back to the user what the
+ * actual zoom was, or for other calculations requiring the current preview crop region.
+ *
+ * <p>None of the parameters are mutated.</p>
+ *
+ * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
+ * @param cropRegion the user-specified crop region
+ * @param previewSize the current preview size (in pixels)
+ * @param params the current camera parameters (not mutated)
+ *
+ * @return the zoom index, and the effective/reported crop regions (relative to active array)
+ */
+ public static ZoomData convertScalerCropRegion(Rect activeArraySize, Rect
+ cropRegion, Size previewSize, Camera.Parameters params) {
+ Rect activeArraySizeOnly = new Rect(
+ /*left*/0, /*top*/0,
+ activeArraySize.width(), activeArraySize.height());
+
+ Rect userCropRegion = cropRegion;
+
+ if (userCropRegion == null) {
+ userCropRegion = activeArraySizeOnly;
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, "convertScalerCropRegion - user crop region was " + userCropRegion);
+ }
+
+ final Rect reportedCropRegion = new Rect();
+ final Rect previewCropRegion = new Rect();
+ final int zoomIdx = ParameterUtils.getClosestAvailableZoomCrop(params, activeArraySizeOnly,
+ previewSize, userCropRegion,
+ /*out*/reportedCropRegion, /*out*/previewCropRegion);
+
+ if (VERBOSE) {
+ Log.v(TAG, "convertScalerCropRegion - zoom calculated to: " +
+ "zoomIndex = " + zoomIdx +
+ ", reported crop region = " + reportedCropRegion +
+ ", preview crop region = " + previewCropRegion);
+ }
+
+ return new ZoomData(zoomIdx, previewCropRegion, reportedCropRegion);
+ }
+
+ /**
+ * Calculate the actual/effective/reported normalized rectangle data from a metering
+ * rectangle.
+ *
+ * <p>If any of the rectangles are out-of-range of their intended bounding box,
+ * the {@link #RECTANGLE_EMPTY empty rectangle} is substituted instead
+ * (with a weight of {@code 0}).</p>
+ *
+ * <p>The metering rectangle is bound by the crop region (effective/reported respectively).
+ * The metering {@link Camera.Area area} is bound by {@code [-1000, 1000]}.</p>
+ *
+ * <p>No parameters are mutated; returns the new metering data.</p>
+ *
+ * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
+ * @param meteringRect the user-specified metering rectangle
+ * @param zoomData the calculated zoom data corresponding to this request
+ *
+ * @return the metering area, the reported/effective metering rectangles
+ */
+ public static MeteringData convertMeteringRectangleToLegacy(
+ Rect activeArray, MeteringRectangle meteringRect, ZoomData zoomData) {
+ Rect previewCrop = zoomData.previewCrop;
+
+ float scaleW = (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN) * 1.0f /
+ previewCrop.width();
+ float scaleH = (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN) * 1.0f /
+ previewCrop.height();
+
+ Matrix transform = new Matrix();
+ // Move the preview crop so that top,left is at (0,0), otherwise after scaling
+ // the corner bounds will be outside of [-1000, 1000]
+ transform.setTranslate(-previewCrop.left, -previewCrop.top);
+ // Scale into [0, 2000] range about the center of the preview
+ transform.postScale(scaleW, scaleH);
+ // Move so that top left of a typical rect is at [-1000, -1000]
+ transform.postTranslate(/*dx*/NORMALIZED_RECTANGLE_MIN, /*dy*/NORMALIZED_RECTANGLE_MIN);
+
+ /*
+ * Calculate the preview metering region (effective), and the camera1 api
+ * normalized metering region.
+ */
+ Rect normalizedRegionUnbounded = ParamsUtils.mapRect(transform, meteringRect.getRect());
+
+ /*
+ * Try to intersect normalized area with [-1000, 1000] rectangle; otherwise
+ * it's completely out of range
+ */
+ Rect normalizedIntersected = new Rect(normalizedRegionUnbounded);
+
+ Camera.Area meteringArea;
+ if (!normalizedIntersected.intersect(NORMALIZED_RECTANGLE_DEFAULT)) {
+ Log.w(TAG,
+ "convertMeteringRectangleToLegacy - metering rectangle too small, " +
+ "no metering will be done");
+ normalizedIntersected.set(RECTANGLE_EMPTY);
+ meteringArea = new Camera.Area(RECTANGLE_EMPTY,
+ MeteringRectangle.METERING_WEIGHT_DONT_CARE);
+ } else {
+ meteringArea = new Camera.Area(normalizedIntersected,
+ meteringRect.getMeteringWeight());
+ }
+
+ /*
+ * Calculate effective preview metering region
+ */
+ Rect previewMetering = meteringRect.getRect();
+ if (!previewMetering.intersect(previewCrop)) {
+ previewMetering.set(RECTANGLE_EMPTY);
+ }
+
+ /*
+ * Calculate effective reported metering region
+ * - Transform the calculated metering area back into active array space
+ * - Clip it to be a subset of the reported crop region
+ */
+ Rect reportedMetering;
+ {
+ Camera.Area normalizedAreaUnbounded = new Camera.Area(
+ normalizedRegionUnbounded, meteringRect.getMeteringWeight());
+ WeightedRectangle reportedMeteringRect = convertCameraAreaToActiveArrayRectangle(
+ activeArray, zoomData, normalizedAreaUnbounded, /*usePreviewCrop*/false);
+ reportedMetering = reportedMeteringRect.rect;
+ }
+
+ if (VERBOSE) {
+ Log.v(TAG, String.format(
+ "convertMeteringRectangleToLegacy - activeArray = %s, meteringRect = %s, " +
+ "previewCrop = %s, meteringArea = %s, previewMetering = %s, " +
+ "reportedMetering = %s, normalizedRegionUnbounded = %s",
+ activeArray, meteringRect,
+ previewCrop, stringFromArea(meteringArea), previewMetering,
+ reportedMetering, normalizedRegionUnbounded));
+ }
+
+ return new MeteringData(meteringArea, previewMetering, reportedMetering);
+ }
+
+ /**
+ * Convert the normalized camera area from [-1000, 1000] coordinate space
+ * into the active array-based coordinate space.
+ *
+ * <p>Values out of range are clipped to be within the resulting (reported) crop
+ * region. It is possible to have values larger than the preview crop.</p>
+ *
+ * <p>Weights out of range of [0, 1000] are clipped to be within the range.</p>
+ *
+ * @param activeArraySize active array size of the sensor (e.g. max jpeg size)
+ * @param zoomData the calculated zoom data corresponding to this request
+ * @param area the normalized camera area
+ *
+ * @return the weighed rectangle in active array coordinate space, with the weight
+ */
+ public static WeightedRectangle convertCameraAreaToActiveArrayRectangle(
+ Rect activeArray, ZoomData zoomData, Camera.Area area) {
+ return convertCameraAreaToActiveArrayRectangle(activeArray, zoomData, area,
+ /*usePreviewCrop*/true);
+ }
+
+ private static WeightedRectangle convertCameraAreaToActiveArrayRectangle(
+ Rect activeArray, ZoomData zoomData, Camera.Area area, boolean usePreviewCrop) {
+ Rect previewCrop = zoomData.previewCrop;
+ Rect reportedCrop = zoomData.reportedCrop;
+
+ float scaleW = previewCrop.width() * 1.0f /
+ (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN);
+ float scaleH = previewCrop.height() * 1.0f /
+ (NORMALIZED_RECTANGLE_MAX - NORMALIZED_RECTANGLE_MIN);
+
+ /*
+ * Calculate the reported metering region from the non-intersected normalized region
+ * by scaling and translating back into active array-relative coordinates.
+ */
+ Matrix transform = new Matrix();
+
+ // Move top left from (-1000, -1000) to (0, 0)
+ transform.setTranslate(/*dx*/NORMALIZED_RECTANGLE_MAX, /*dy*/NORMALIZED_RECTANGLE_MAX);
+
+ // Scale from [0, 2000] back into the preview rectangle
+ transform.postScale(scaleW, scaleH);
+
+ // Move the rect so that the [-1000,-1000] point ends up at the preview [left, top]
+ transform.postTranslate(previewCrop.left, previewCrop.top);
+
+ Rect cropToIntersectAgainst = usePreviewCrop ? previewCrop : reportedCrop;
+
+ // Now apply the transformation backwards to get the reported metering region
+ Rect reportedMetering = ParamsUtils.mapRect(transform, area.rect);
+ // Intersect it with the crop region, to avoid reporting out-of-bounds
+ // metering regions
+ if (!reportedMetering.intersect(cropToIntersectAgainst)) {
+ reportedMetering.set(RECTANGLE_EMPTY);
+ }
+
+ int weight = area.weight;
+ if (weight < MeteringRectangle.METERING_WEIGHT_MIN) {
+ Log.w(TAG,
+ "convertCameraAreaToMeteringRectangle - rectangle "
+ + stringFromArea(area) + " has too small weight, clip to 0");
+ weight = 0;
+ }
+
+ return new WeightedRectangle(reportedMetering, area.weight);
+ }
+
+
private ParameterUtils() {
throw new AssertionError();
}
diff --git a/core/java/android/hardware/camera2/params/MeteringRectangle.java b/core/java/android/hardware/camera2/params/MeteringRectangle.java
index 93fd053..b1cea57 100644
--- a/core/java/android/hardware/camera2/params/MeteringRectangle.java
+++ b/core/java/android/hardware/camera2/params/MeteringRectangle.java
@@ -100,6 +100,8 @@
/**
* Create a new metering rectangle.
*
+ * <p>The point {@code xy}'s data is copied; the reference is not retained.</p>
+ *
* @param xy a non-{@code null} {@link Point} with both x,y >= 0
* @param dimensions a non-{@code null} {@link android.util.Size Size} with width, height >= 0
* @param meteringWeight weight >= 0
@@ -121,6 +123,8 @@
/**
* Create a new metering rectangle.
*
+ * <p>The rectangle data is copied; the reference is not retained.</p>
+ *
* @param rect a non-{@code null} rectangle with all x,y,w,h dimensions >= 0
* @param meteringWeight weight >= 0
*
@@ -185,7 +189,7 @@
/**
* Convenience method to create the upper-left (X,Y) coordinate as a {@link Point}.
*
- * @return {@code (x,y)} point with both x,y >= 0
+ * @return a new {@code (x,y)} {@link Point} with both x,y >= 0
*/
public Point getUpperLeftPoint() {
return new Point(mX, mY);
@@ -196,7 +200,7 @@
*
* <p>This strips away the X,Y,weight from the rectangle.</p>
*
- * @return a Size with non-negative width and height
+ * @return a new {@link Size} with non-negative width and height
*/
public Size getSize() {
return new Size(mWidth, mHeight);
@@ -207,7 +211,7 @@
*
* <p>This strips away the weight from the rectangle.</p>
*
- * @return a {@link Rect} with non-negative x1, y1, x2, y2
+ * @return a new {@link Rect} with non-negative x1, y1, x2, y2
*/
public Rect getRect() {
return new Rect(mX, mY, mX + mWidth, mY + mHeight);
@@ -250,4 +254,16 @@
public int hashCode() {
return HashCodeHelpers.hashCode(mX, mY, mWidth, mHeight, mWeight);
}
+
+ /**
+ * Return the metering rectangle as a string representation
+ * {@code "(x:%d, y:%d, w:%d, h:%d, wt:%d)"} where each {@code %d} respectively represents
+ * the x, y, width, height, and weight points.
+ *
+ * @return string representation of the metering rectangle
+ */
+ @Override
+ public String toString() {
+ return String.format("(x:%d, y:%d, w:%d, h:%d, wt:%d)", mX, mY, mWidth, mHeight, mWeight);
+ }
}
diff --git a/core/java/android/hardware/camera2/utils/ParamsUtils.java b/core/java/android/hardware/camera2/utils/ParamsUtils.java
index cd39f71..232a4f6 100644
--- a/core/java/android/hardware/camera2/utils/ParamsUtils.java
+++ b/core/java/android/hardware/camera2/utils/ParamsUtils.java
@@ -16,8 +16,10 @@
package android.hardware.camera2.utils;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.Rational;
import android.util.Size;
import static com.android.internal.util.Preconditions.*;
@@ -27,6 +29,9 @@
*/
public class ParamsUtils {
+ /** Arbitrary denominator used to estimate floats as rationals */
+ private static final int RATIONAL_DENOMINATOR = 1000000; // 1million
+
/**
* Create a {@link Rect} from a {@code Size} by creating a new rectangle with
* left, top = {@code (0, 0)} and right, bottom = {@code (width, height)}
@@ -45,7 +50,9 @@
/**
* Create a {@link Rect} from a {@code RectF} by creating a new rectangle with
- * each corner (left, top, right, bottom) rounded towards the nearest integer value.
+ * each corner (left, top, right, bottom) rounded towards the nearest integer bounding box.
+ *
+ * <p>In particular (left, top) is floored, and (right, bottom) is ceiled.</p>
*
* @param size a non-{@code null} rect
*
@@ -57,12 +64,34 @@
checkNotNull(rect, "rect must not be null");
Rect r = new Rect();
- rect.round(r);
+ rect.roundOut(r);
return r;
}
/**
+ * Map the rectangle in {@code rect} with the transform in {@code transform} into
+ * a new rectangle, with each corner (left, top, right, bottom) rounded towards the nearest
+ * integer bounding box.
+ *
+ * <p>None of the arguments are mutated.</p>
+ *
+ * @param transform a non-{@code null} transformation matrix
+ * @param rect a non-{@code null} rectangle
+ * @return a new rectangle that was transformed by {@code transform}
+ *
+ * @throws NullPointerException if any of the args were {@code null}
+ */
+ public static Rect mapRect(Matrix transform, Rect rect) {
+ checkNotNull(transform, "transform must not be null");
+ checkNotNull(rect, "rect must not be null");
+
+ RectF rectF = new RectF(rect);
+ transform.mapRect(rectF);
+ return createRect(rectF);
+ }
+
+ /**
* Create a {@link Size} from a {@code Rect} by creating a new size whose width
* and height are the same as the rectangle's width and heights.
*
@@ -79,10 +108,59 @@
}
/**
- * Convert an integral rectangle ({@code size}) to a floating point rectangle
+ * Create a {@link Rational} value by approximating the float value as a rational.
+ *
+ * <p>Floating points too large to be represented as an integer will be converted to
+ * to {@link Integer#MAX_VALUE}; floating points too small to be represented as an integer
+ * will be converted to {@link Integer#MIN_VALUE}.</p>
+ *
+ * @param value a floating point value
+ * @return the rational representation of the float
+ */
+ public static Rational createRational(float value) {
+ if (Float.isNaN(value)) {
+ return Rational.NaN;
+ } else if (value == Float.POSITIVE_INFINITY) {
+ return Rational.POSITIVE_INFINITY;
+ } else if (value == Float.NEGATIVE_INFINITY) {
+ return Rational.NEGATIVE_INFINITY;
+ } else if (value == 0.0f) {
+ return Rational.ZERO;
+ }
+
+ // normal finite value: approximate it
+
+ /*
+ * Start out trying to approximate with denominator = 1million,
+ * but if the numerator doesn't fit into an Int then keep making the denominator
+ * smaller until it does.
+ */
+ int den = RATIONAL_DENOMINATOR;
+ float numF;
+ do {
+ numF = value * den;
+
+ if ((numF > Integer.MIN_VALUE && numF < Integer.MAX_VALUE) || (den == 1)) {
+ break;
+ }
+
+ den /= 10;
+ } while (true);
+
+ /*
+ * By float -> int narrowing conversion in JLS 5.1.3, this will automatically become
+ * MIN_VALUE or MAX_VALUE if numF is too small/large to be represented by an integer
+ */
+ int num = (int) numF;
+
+ return new Rational(num, den);
+ }
+
+ /**
+ * Convert an integral rectangle ({@code source}) to a floating point rectangle
* ({@code destination}) in-place.
*
- * @param size the originating integer rectangle will be read from here
+ * @param source the originating integer rectangle will be read from here
* @param destination the resulting floating point rectangle will be written out to here
*
* @throws NullPointerException if {@code rect} was {@code null}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/hardware/location/ActivityChangedEvent.aidl
similarity index 84%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to core/java/android/hardware/location/ActivityChangedEvent.aidl
index 3da1321..21f2445 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/hardware/location/ActivityChangedEvent.aidl
@@ -11,9 +11,9 @@
* 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.
+ * limitations under the License
*/
-package android.bluetooth.le;
+package android.hardware.location;
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable ActivityChangedEvent;
\ No newline at end of file
diff --git a/core/java/android/hardware/location/ActivityChangedEvent.java b/core/java/android/hardware/location/ActivityChangedEvent.java
new file mode 100644
index 0000000..0a89207
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityChangedEvent.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.hardware.location;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.InvalidParameterException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ *
+ * @hide
+ */
+public class ActivityChangedEvent implements Parcelable {
+ private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+ public ActivityChangedEvent(ActivityRecognitionEvent[] activityRecognitionEvents) {
+ if (activityRecognitionEvents == null) {
+ throw new InvalidParameterException(
+ "Parameter 'activityRecognitionEvents' must not be null.");
+ }
+
+ mActivityRecognitionEvents = Arrays.asList(activityRecognitionEvents);
+ }
+
+ @NonNull
+ public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+ return mActivityRecognitionEvents;
+ }
+
+ public static final Creator<ActivityChangedEvent> CREATOR =
+ new Creator<ActivityChangedEvent>() {
+ @Override
+ public ActivityChangedEvent createFromParcel(Parcel source) {
+ int activityRecognitionEventsLength = source.readInt();
+ ActivityRecognitionEvent[] activityRecognitionEvents =
+ new ActivityRecognitionEvent[activityRecognitionEventsLength];
+ source.readTypedArray(activityRecognitionEvents, ActivityRecognitionEvent.CREATOR);
+
+ return new ActivityChangedEvent(activityRecognitionEvents);
+ }
+
+ @Override
+ public ActivityChangedEvent[] newArray(int size) {
+ return new ActivityChangedEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ ActivityRecognitionEvent[] activityRecognitionEventArray =
+ mActivityRecognitionEvents.toArray(new ActivityRecognitionEvent[0]);
+ parcel.writeInt(activityRecognitionEventArray.length);
+ parcel.writeTypedArray(activityRecognitionEventArray, flags);
+ }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionEvent.java b/core/java/android/hardware/location/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..5aeb899
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionEvent.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ *
+ * @hide
+ */
+public class ActivityRecognitionEvent implements Parcelable {
+ private final String mActivity;
+ private final int mEventType;
+ private final long mTimestampNs;
+
+ public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+ mActivity = activity;
+ mEventType = eventType;
+ mTimestampNs = timestampNs;
+ }
+
+ public String getActivity() {
+ return mActivity;
+ }
+
+ public int getEventType() {
+ return mEventType;
+ }
+
+ public long getTimestampNs() {
+ return mTimestampNs;
+ }
+
+ public static final Creator<ActivityRecognitionEvent> CREATOR =
+ new Creator<ActivityRecognitionEvent>() {
+ @Override
+ public ActivityRecognitionEvent createFromParcel(Parcel source) {
+ String activity = source.readString();
+ int eventType = source.readInt();
+ long timestampNs = source.readLong();
+
+ return new ActivityRecognitionEvent(activity, eventType, timestampNs);
+ }
+
+ @Override
+ public ActivityRecognitionEvent[] newArray(int size) {
+ return new ActivityRecognitionEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mActivity);
+ parcel.writeInt(mEventType);
+ parcel.writeLong(mTimestampNs);
+ }
+}
diff --git a/core/java/android/hardware/location/ActivityRecognitionHardware.java b/core/java/android/hardware/location/ActivityRecognitionHardware.java
new file mode 100644
index 0000000..a4ce4ac
--- /dev/null
+++ b/core/java/android/hardware/location/ActivityRecognitionHardware.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.hardware.location;
+
+import android.Manifest;
+import android.content.Context;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * A class that implements an {@link IActivityRecognitionHardware} backed up by the Activity
+ * Recognition HAL.
+ *
+ * @hide
+ */
+public class ActivityRecognitionHardware extends IActivityRecognitionHardware.Stub {
+ private static final String TAG = "ActivityRecognitionHardware";
+
+ private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
+ private static final int INVALID_ACTIVITY_TYPE = -1;
+ private static final int NATIVE_SUCCESS_RESULT = 0;
+
+ private static ActivityRecognitionHardware sSingletonInstance = null;
+ private static final Object sSingletonInstanceLock = new Object();
+
+ private final Context mContext;
+ private final String[] mSupportedActivities;
+
+ private final RemoteCallbackList<IActivityRecognitionHardwareSink> mSinks =
+ new RemoteCallbackList<IActivityRecognitionHardwareSink>();
+
+ private static class Event {
+ public int activity;
+ public int type;
+ public long timestamp;
+ }
+
+ private ActivityRecognitionHardware(Context context) {
+ nativeInitialize();
+
+ mContext = context;
+ mSupportedActivities = fetchSupportedActivities();
+ }
+
+ public static ActivityRecognitionHardware getInstance(Context context) {
+ synchronized (sSingletonInstanceLock) {
+ if (sSingletonInstance == null) {
+ sSingletonInstance = new ActivityRecognitionHardware(context);
+ }
+
+ return sSingletonInstance;
+ }
+ }
+
+ public static boolean isSupported() {
+ return nativeIsSupported();
+ }
+
+ @Override
+ public String[] getSupportedActivities() {
+ checkPermissions();
+ return mSupportedActivities;
+ }
+
+ @Override
+ public boolean isActivitySupported(String activity) {
+ checkPermissions();
+ int activityType = getActivityType(activity);
+ return activityType != INVALID_ACTIVITY_TYPE;
+ }
+
+ @Override
+ public boolean registerSink(IActivityRecognitionHardwareSink sink) {
+ checkPermissions();
+ return mSinks.register(sink);
+ }
+
+ @Override
+ public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
+ checkPermissions();
+ return mSinks.unregister(sink);
+ }
+
+ @Override
+ public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) {
+ checkPermissions();
+
+ int activityType = getActivityType(activity);
+ if (activityType == INVALID_ACTIVITY_TYPE) {
+ return false;
+ }
+
+ int result = nativeEnableActivityEvent(activityType, eventType, reportLatencyNs);
+ return result == NATIVE_SUCCESS_RESULT;
+ }
+
+ @Override
+ public boolean disableActivityEvent(String activity, int eventType) {
+ checkPermissions();
+
+ int activityType = getActivityType(activity);
+ if (activityType == INVALID_ACTIVITY_TYPE) {
+ return false;
+ }
+
+ int result = nativeDisableActivityEvent(activityType, eventType);
+ return result == NATIVE_SUCCESS_RESULT;
+ }
+
+ @Override
+ public boolean flush() {
+ checkPermissions();
+ int result = nativeFlush();
+ return result == NATIVE_SUCCESS_RESULT;
+ }
+
+ /**
+ * Called by the Activity-Recognition HAL.
+ */
+ private void onActivityChanged(Event[] events) {
+ int size = mSinks.beginBroadcast();
+ if (size == 0 || events == null || events.length == 0) {
+ return;
+ }
+
+ int eventsLength = events.length;
+ ActivityRecognitionEvent activityRecognitionEventArray[] =
+ new ActivityRecognitionEvent[eventsLength];
+ for (int i = 0; i < eventsLength; ++i) {
+ Event event = events[i];
+ String activityName = getActivityName(event.activity);
+ activityRecognitionEventArray[i] =
+ new ActivityRecognitionEvent(activityName, event.type, event.timestamp);
+ }
+ ActivityChangedEvent activityChangedEvent =
+ new ActivityChangedEvent(activityRecognitionEventArray);
+
+ for (int i = 0; i < size; ++i) {
+ IActivityRecognitionHardwareSink sink = mSinks.getBroadcastItem(i);
+ try {
+ sink.onActivityChanged(activityChangedEvent);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delivering activity changed event.", e);
+ }
+ }
+ mSinks.finishBroadcast();
+
+ }
+
+ private String getActivityName(int activityType) {
+ if (activityType < 0 || activityType >= mSupportedActivities.length) {
+ String message = String.format(
+ "Invalid ActivityType: %d, SupportedActivities: %d",
+ activityType,
+ mSupportedActivities.length);
+ Log.e(TAG, message);
+ return null;
+ }
+
+ return mSupportedActivities[activityType];
+ }
+
+ private int getActivityType(String activity) {
+ if (TextUtils.isEmpty(activity)) {
+ return INVALID_ACTIVITY_TYPE;
+ }
+
+ int supporteActivitiesLength = mSupportedActivities.length;
+ for (int i = 0; i < supporteActivitiesLength; ++i) {
+ if (activity.equals(mSupportedActivities[i])) {
+ return i;
+ }
+ }
+
+ return INVALID_ACTIVITY_TYPE;
+ }
+
+ private void checkPermissions() {
+ String message = String.format(
+ "Permission '%s' not granted to access ActivityRecognitionHardware",
+ HARDWARE_PERMISSION);
+ mContext.enforceCallingPermission(HARDWARE_PERMISSION, message);
+ }
+
+ private static String[] fetchSupportedActivities() {
+ String[] supportedActivities = nativeGetSupportedActivities();
+ if (supportedActivities != null) {
+ return supportedActivities;
+ }
+
+ return new String[0];
+ }
+
+ // native bindings
+ static { nativeClassInit(); }
+
+ private static native void nativeClassInit();
+ private static native void nativeInitialize();
+ private static native void nativeRelease();
+ private static native boolean nativeIsSupported();
+ private static native String[] nativeGetSupportedActivities();
+ private static native int nativeEnableActivityEvent(
+ int activityType,
+ int eventType,
+ long reportLatenceNs);
+ private static native int nativeDisableActivityEvent(int activityType, int eventType);
+ private static native int nativeFlush();
+}
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardware.aidl b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
new file mode 100644
index 0000000..bc6b183
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardware.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/license/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.location;
+
+import android.hardware.location.IActivityRecognitionHardwareSink;
+
+/**
+ * Activity Recognition Hardware provider interface.
+ * This interface can be used to implement hardware based activity recognition.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardware {
+ /**
+ * Gets an array of supported activities by hardware.
+ */
+ String[] getSupportedActivities();
+
+ /**
+ * Returns true if the given activity is supported, false otherwise.
+ */
+ boolean isActivitySupported(in String activityType);
+
+ /**
+ * Registers a sink with Hardware Activity-Recognition.
+ */
+ boolean registerSink(in IActivityRecognitionHardwareSink sink);
+
+ /**
+ * Unregisters a sink with Hardware Activity-Recognition.
+ */
+ boolean unregisterSink(in IActivityRecognitionHardwareSink sink);
+
+ /**
+ * Enables tracking of a given activity/event type, if the activity is supported.
+ */
+ boolean enableActivityEvent(in String activityType, int eventType, long reportLatencyNs);
+
+ /**
+ * Disables tracking of a given activity/eventy type.
+ */
+ boolean disableActivityEvent(in String activityType, int eventType);
+
+ /**
+ * Requests hardware for all the activity events detected up to the given point in time.
+ */
+ boolean flush();
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
new file mode 100644
index 0000000..21c8e87
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareSink.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/license/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.location;
+
+import android.hardware.location.ActivityChangedEvent;
+
+/**
+ * Activity Recognition Hardware provider Sink interface.
+ * This interface can be used to implement sinks to receive activity notifications.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardwareSink {
+ /**
+ * Activity changed event.
+ */
+ void onActivityChanged(in ActivityChangedEvent event);
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
new file mode 100644
index 0000000..0507f52
--- /dev/null
+++ b/core/java/android/hardware/location/IActivityRecognitionHardwareWatcher.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/license/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.location;
+
+import android.hardware.location.IActivityRecognitionHardware;
+
+/**
+ * Activity Recognition Hardware watcher. This interface can be used to receive interfaces to
+ * implementations of {@link IActivityRecognitionHardware}.
+ *
+ * @hide
+ */
+interface IActivityRecognitionHardwareWatcher {
+ /**
+ * Hardware Activity-Recognition availability event.
+ */
+ void onInstanceChanged(in IActivityRecognitionHardware instance);
+}
\ No newline at end of file
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 3417de1..8423d09 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -53,6 +53,7 @@
import android.view.animation.AnimationUtils;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
@@ -251,18 +252,6 @@
*/
public static final int IME_VISIBLE = 0x2;
- /**
- * The IME does not require cursor/anchor position.
- */
- public static final int CURSOR_ANCHOR_MONITOR_MODE_NONE = 0x0;
-
- /**
- * Passing this flag into a call to {@link #setCursorAnchorMonitorMode(int)} will result in
- * the cursor rectangle being provided in screen coordinates to subsequent
- * {@link #onUpdateCursor(Rect)} callbacks.
- */
- public static final int CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT = 0x1;
-
InputMethodManager mImm;
int mTheme = 0;
@@ -1722,8 +1711,9 @@
* Called when the application has reported a new location of its text cursor. This is only
* called if explicitly requested by the input method. The default implementation does nothing.
* @param newCursor The new cursor position, in screen coordinates if the input method calls
- * {@link #setCursorAnchorMonitorMode} with {@link #CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT}.
- * Otherwise, this is in local coordinates.
+ * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} with
+ * {@link CursorAnchorInfoRequest#FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES}. Otherwise,
+ * this is in local coordinates.
*/
public void onUpdateCursor(Rect newCursor) {
// Intentionally empty
@@ -1741,13 +1731,6 @@
}
/**
- * Update the cursor/anthor monitor mode.
- */
- public void setCursorAnchorMonitorMode(int monitorMode) {
- mImm.setCursorAnchorMonitorMode(mToken, monitorMode);
- }
-
- /**
* Close this input method's soft input area, removing it from the display.
* The input method will continue running, but the user can no longer use
* it to generate input by touching the screen.
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
index 79db389..58d0048 100644
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -45,7 +45,7 @@
protected NetworkInfo mNetworkInfo;
protected LinkProperties mLinkProperties;
protected NetworkCapabilities mNetworkCapabilities;
- protected Network mNetwork = new Network(ConnectivityManager.INVALID_NET_ID);
+ protected Network mNetwork = new Network(ConnectivityManager.NETID_UNSET);
private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a7e03fc..8de545e 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -532,12 +532,14 @@
/**
* @hide
*/
- public final static int INVALID_NET_ID = 0;
+ public final static int REQUEST_ID_UNSET = 0;
/**
+ * A NetID indicating no Network is selected.
+ * Keep in sync with bionic/libc/dns/include/resolv_netid.h
* @hide
*/
- public final static int REQUEST_ID_UNSET = 0;
+ public static final int NETID_UNSET = 0;
private final IConnectivityManager mService;
@@ -2508,11 +2510,7 @@
* @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
*/
public static boolean setProcessDefaultNetwork(Network network) {
- if (network == null) {
- return NetworkUtils.unbindProcessToNetwork();
- } else {
- return NetworkUtils.bindProcessToNetwork(network.netId);
- }
+ return NetworkUtils.bindProcessToNetwork(network == null ? NETID_UNSET : network.netId);
}
/**
@@ -2523,7 +2521,7 @@
*/
public static Network getProcessDefaultNetwork() {
int netId = NetworkUtils.getNetworkBoundToProcess();
- if (netId == 0) return null;
+ if (netId == NETID_UNSET) return null;
return new Network(netId);
}
@@ -2538,10 +2536,7 @@
* @deprecated This is strictly for legacy usage to support {@link #startUsingNetworkFeature}.
*/
public static boolean setProcessDefaultNetworkForHostResolution(Network network) {
- if (network == null) {
- return NetworkUtils.unbindProcessToNetworkForHostResolution();
- } else {
- return NetworkUtils.bindProcessToNetworkForHostResolution(network.netId);
- }
+ return NetworkUtils.bindProcessToNetworkForHostResolution(
+ network == null ? NETID_UNSET : network.netId);
}
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index b9c6491..f61984a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -42,9 +42,6 @@
/** {@hide} */
interface IConnectivityManager
{
- // Keep this in sync with framework/native/services/connectivitymanager/ConnectivityManager.h
- void markSocketAsUser(in ParcelFileDescriptor socket, int uid);
-
NetworkInfo getActiveNetworkInfo();
NetworkInfo getActiveNetworkInfoForUid(int uid);
NetworkInfo getNetworkInfo(int networkType);
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index aa1e123..9b95305 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -106,27 +106,16 @@
public native static String getDhcpError();
/**
- * Set the SO_MARK of {@code socketfd} to {@code mark}
- */
- public native static void markSocket(int socketfd, int mark);
-
- /**
* Binds the current process to the network designated by {@code netId}. All sockets created
* in the future (and not explicitly bound via a bound {@link SocketFactory} (see
* {@link Network#getSocketFactory}) will be bound to this network. Note that if this
* {@code Network} ever disconnects all sockets created in this way will cease to work. This
* is by design so an application doesn't accidentally use sockets it thinks are still bound to
- * a particular {@code Network}.
+ * a particular {@code Network}. Passing NETID_UNSET clears the binding.
*/
public native static boolean bindProcessToNetwork(int netId);
/**
- * Clear any process specific {@code Network} binding. This reverts a call to
- * {@link #bindProcessToNetwork}.
- */
- public native static boolean unbindProcessToNetwork();
-
- /**
* Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
* {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
*/
@@ -134,21 +123,14 @@
/**
* Binds host resolutions performed by this process to the network designated by {@code netId}.
- * {@link #bindProcessToNetwork} takes precedence over this setting.
+ * {@link #bindProcessToNetwork} takes precedence over this setting. Passing NETID_UNSET clears
+ * the binding.
*
* @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
*/
public native static boolean bindProcessToNetworkForHostResolution(int netId);
/**
- * Clears any process specific {@link Network} binding for host resolution. This does
- * not clear bindings enacted via {@link #bindProcessToNetwork}.
- *
- * @deprecated This is strictly for legacy usage to support startUsingNetworkFeature().
- */
- public native static boolean unbindProcessToNetworkForHostResolution();
-
- /**
* Explicitly binds {@code socketfd} to the network designated by {@code netId}. This
* overrides any binding via {@link #bindProcessToNetwork}.
*/
diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java
index 691afdd..1e1ad9e 100644
--- a/core/java/android/os/FileBridge.java
+++ b/core/java/android/os/FileBridge.java
@@ -31,7 +31,6 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.SyncFailedException;
import java.nio.ByteOrder;
import java.util.Arrays;
@@ -144,8 +143,7 @@
}
}
- @Override
- public void flush() throws IOException {
+ public void fsync() throws IOException {
writeCommandAndBlock(CMD_FSYNC, "fsync()");
}
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index d997e44..207dc4a 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -336,26 +336,6 @@
void removeVpnUidRanges(int netId, in UidRange[] ranges);
/**
- * Get the SO_MARK associated with routing packets for user {@code uid}
- */
- int getMarkForUid(int uid);
-
- /**
- * Get the SO_MARK associated with protecting packets from VPN routing rules
- */
- int getMarkForProtect();
-
- /**
- * Route all traffic in {@code route} to {@code iface} setup for marked forwarding
- */
- void setMarkedForwardingRoute(String iface, in RouteInfo route);
-
- /**
- * Clear routes set by {@link setMarkedForwardingRoute}
- */
- void clearMarkedForwardingRoute(String iface, in RouteInfo route);
-
- /**
* Exempts {@code host} from the routing set up by {@link setMarkedForwardingRoute}
* All connects to {@code host} will use the global routing table
*/
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index a54320f..707d31a 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -15,6 +15,7 @@
*/
package android.os;
+import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.content.Context;
import android.content.pm.UserInfo;
@@ -900,6 +901,8 @@
public static int getMaxSupportedUsers() {
// Don't allow multiple users on certain builds
if (android.os.Build.ID.startsWith("JVP")) return 1;
+ // Svelte devices don't get multi-user.
+ if (ActivityManager.isLowRamDeviceStatic()) return 1;
return SystemProperties.getInt("fw.max_users",
Resources.getSystem().getInteger(R.integer.config_multiuserMaximumUsers));
}
diff --git a/core/java/android/preference/SwitchPreference.java b/core/java/android/preference/SwitchPreference.java
index 76ef544..46be928 100644
--- a/core/java/android/preference/SwitchPreference.java
+++ b/core/java/android/preference/SwitchPreference.java
@@ -36,10 +36,11 @@
* @attr ref android.R.styleable#SwitchPreference_disableDependentsState
*/
public class SwitchPreference extends TwoStatePreference {
+ private final Listener mListener = new Listener();
+
// Switch text for on and off states
private CharSequence mSwitchOn;
private CharSequence mSwitchOff;
- private final Listener mListener = new Listener();
private class Listener implements CompoundButton.OnCheckedChangeListener {
@Override
@@ -122,6 +123,11 @@
View checkableView = view.findViewById(com.android.internal.R.id.switchWidget);
if (checkableView != null && checkableView instanceof Checkable) {
+ if (checkableView instanceof Switch) {
+ final Switch switchView = (Switch) checkableView;
+ switchView.setOnCheckedChangeListener(null);
+ }
+
((Checkable) checkableView).setChecked(mChecked);
sendAccessibilityEvent(checkableView);
diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java
index 8c229c5..8bc157a 100644
--- a/core/java/android/print/PageRange.java
+++ b/core/java/android/print/PageRange.java
@@ -87,7 +87,7 @@
* @hide
*/
public boolean contains(int pageIndex) {
- return pageIndex >= mStart && pageIndex <= mEnd;
+ return (pageIndex >= mStart) && (pageIndex <= mEnd);
}
/**
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 7ec838e..9361286 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -105,7 +105,7 @@
private static final String LOG_TAG = "PrintManager";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1;
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 760f2a5..c6b6e20 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -408,7 +408,7 @@
values.put(CACHED_NUMBER_LABEL, ci.numberLabel);
}
- if ((ci != null) && (ci.person_id > 0)) {
+ if ((ci != null) && (ci.contactIdOrZero > 0)) {
// Update usage information for the number associated with the contact ID.
// We need to use both the number and the ID for obtaining a data ID since other
// contacts may have the same number.
@@ -422,7 +422,8 @@
cursor = resolver.query(Phone.CONTENT_URI,
new String[] { Phone._ID },
Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?",
- new String[] { String.valueOf(ci.person_id), normalizedPhoneNumber},
+ new String[] { String.valueOf(ci.contactIdOrZero),
+ normalizedPhoneNumber},
null);
} else {
final String phoneNumber = ci.phoneNumber != null ? ci.phoneNumber : number;
@@ -431,7 +432,7 @@
Uri.encode(phoneNumber)),
new String[] { Phone._ID },
Phone.CONTACT_ID + " =?",
- new String[] { String.valueOf(ci.person_id) },
+ new String[] { String.valueOf(ci.contactIdOrZero) },
null);
}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 2e9077a..e3e328c 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -108,6 +108,16 @@
}
@Override
+ public IVoiceInteractorRequest startCompleteVoice(String callingPackage,
+ IVoiceInteractorCallback callback, CharSequence message, Bundle extras) {
+ Request request = newRequest(callback);
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageOOOO(MSG_START_COMPLETE_VOICE,
+ new Caller(callingPackage, Binder.getCallingUid()), request,
+ message, extras));
+ return request.mInterface;
+ }
+
+ @Override
public IVoiceInteractorRequest startAbortVoice(String callingPackage,
IVoiceInteractorCallback callback, CharSequence message, Bundle extras) {
Request request = newRequest(callback);
@@ -208,6 +218,16 @@
}
}
+ public void sendCompleteVoiceResult(Bundle result) {
+ try {
+ if (DEBUG) Log.d(TAG, "sendCompleteVoiceResult: req=" + mInterface
+ + " result=" + result);
+ finishRequest();
+ mCallback.deliverCompleteVoiceResult(mInterface, result);
+ } catch (RemoteException e) {
+ }
+ }
+
public void sendAbortVoiceResult(Bundle result) {
try {
if (DEBUG) Log.d(TAG, "sendConfirmResult: req=" + mInterface
@@ -249,10 +269,11 @@
}
static final int MSG_START_CONFIRMATION = 1;
- static final int MSG_START_ABORT_VOICE = 2;
- static final int MSG_START_COMMAND = 3;
- static final int MSG_SUPPORTS_COMMANDS = 4;
- static final int MSG_CANCEL = 5;
+ static final int MSG_START_COMPLETE_VOICE = 2;
+ static final int MSG_START_ABORT_VOICE = 3;
+ static final int MSG_START_COMMAND = 4;
+ static final int MSG_SUPPORTS_COMMANDS = 5;
+ static final int MSG_CANCEL = 6;
static final int MSG_TASK_STARTED = 100;
static final int MSG_TASK_FINISHED = 101;
@@ -271,6 +292,13 @@
onConfirm((Caller)args.arg1, (Request)args.arg2, (CharSequence)args.arg3,
(Bundle)args.arg4);
break;
+ case MSG_START_COMPLETE_VOICE:
+ args = (SomeArgs)msg.obj;
+ if (DEBUG) Log.d(TAG, "onCompleteVoice: req=" + ((Request) args.arg2).mInterface
+ + " message=" + args.arg3 + " extras=" + args.arg4);
+ onCompleteVoice((Caller) args.arg1, (Request) args.arg2,
+ (CharSequence) args.arg3, (Bundle) args.arg4);
+ break;
case MSG_START_ABORT_VOICE:
args = (SomeArgs)msg.obj;
if (DEBUG) Log.d(TAG, "onAbortVoice: req=" + ((Request) args.arg2).mInterface
@@ -701,6 +729,27 @@
Bundle extras);
/**
+ * Request to complete the voice interaction session because the voice activity successfully
+ * completed its interaction using voice. Corresponds to
+ * {@link android.app.VoiceInteractor.CompleteVoiceRequest
+ * VoiceInteractor.CompleteVoiceRequest}. The default implementation just sends an empty
+ * confirmation back to allow the activity to exit.
+ *
+ * @param caller Who is making the request.
+ * @param request The active request.
+ * @param message The message informing the user of the problem, as per
+ * {@link android.app.VoiceInteractor.CompleteVoiceRequest
+ * VoiceInteractor.CompleteVoiceRequest}.
+ * @param extras Any additional information, as per
+ * {@link android.app.VoiceInteractor.CompleteVoiceRequest
+ * VoiceInteractor.CompleteVoiceRequest}.
+ */
+ public void onCompleteVoice(Caller caller, Request request, CharSequence message,
+ Bundle extras) {
+ request.sendCompleteVoiceResult(null);
+ }
+
+ /**
* Request to abort the voice interaction session because the voice activity can not
* complete its interaction using voice. Corresponds to
* {@link android.app.VoiceInteractor.AbortVoiceRequest
diff --git a/core/java/android/transition/MoveImage.java b/core/java/android/transition/MoveImage.java
deleted file mode 100644
index 2f7945d..0000000
--- a/core/java/android/transition/MoveImage.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.transition;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-/**
- * TO BE REMOVED.
- * Use ChangeImageTransform + ChangeBounds instead.
- * @hide
- */
-public class MoveImage extends TransitionSet {
-
- public MoveImage() {
- init();
- }
-
- public MoveImage(Context context, AttributeSet attrs) {
- super(context, attrs);
- init();
- }
-
- private void init() {
- addTransition(new ChangeBounds());
- addTransition(new ChangeClipBounds());
- addTransition(new ChangeTransform());
- addTransition(new ChangeImageTransform());
- }
-}
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index fd3e450..68b0a43 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -149,8 +149,6 @@
transition = new Slide(mContext, attrs);
} else if ("explode".equals(name)) {
transition = new Explode(mContext, attrs);
- } else if ("moveImage".equals(name)) {
- transition = new MoveImage(mContext, attrs);
} else if ("changeImageTransform".equals(name)) {
transition = new ChangeImageTransform(mContext, attrs);
} else if ("changeTransform".equals(name)) {
diff --git a/core/java/android/util/ExceptionUtils.java b/core/java/android/util/ExceptionUtils.java
new file mode 100644
index 0000000..6aae84d
--- /dev/null
+++ b/core/java/android/util/ExceptionUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import java.io.IOException;
+
+/**
+ * Utility methods for proxying richer exceptions across Binder calls.
+ *
+ * @hide
+ */
+public class ExceptionUtils {
+ // TODO: longer term these should be replaced with first-class
+ // Parcel.read/writeException() and AIDL support, but for now do this using
+ // a nasty hack.
+
+ private static final String PREFIX_IO = "\u2603";
+
+ public static RuntimeException wrap(IOException e) {
+ throw new IllegalStateException(PREFIX_IO + e.getMessage());
+ }
+
+ public static void maybeUnwrapIOException(RuntimeException e) throws IOException {
+ if ((e instanceof IllegalStateException) && e.getMessage().startsWith(PREFIX_IO)) {
+ throw new IOException(e.getMessage().substring(PREFIX_IO.length()));
+ }
+ }
+}
diff --git a/core/java/android/util/Range.java b/core/java/android/util/Range.java
index 3907e77..211d01a 100644
--- a/core/java/android/util/Range.java
+++ b/core/java/android/util/Range.java
@@ -99,16 +99,16 @@
/**
* Checks if the {@code value} is within the bounds of this range.
*
- * <p>A value is considered to be within this range if it's {@code >=} then
- * the lower endpoint <i>and</i> {@code <=} to the upper endpoint (using the {@link Comparable}
- * interface.</p>
+ * <p>A value is considered to be within this range if it's {@code >=}
+ * the lower endpoint <i>and</i> {@code <=} the upper endpoint (using the {@link Comparable}
+ * interface.)</p>
*
* @param value a non-{@code null} {@code T} reference
* @return {@code true} if the value is within this inclusive range, {@code false} otherwise
*
* @throws NullPointerException if {@code value} was {@code null}
*/
- public boolean inRange(T value) {
+ public boolean contains(T value) {
checkNotNull(value, "value must not be null");
boolean gteLower = value.compareTo(mLower) >= 0;
@@ -118,6 +118,26 @@
}
/**
+ * Checks if another {@code range} is within the bounds of this range.
+ *
+ * <p>A range is considered to be within this range if both of its endpoints
+ * are within this range.</p>
+ *
+ * @param range a non-{@code null} {@code T} reference
+ * @return {@code true} if the range is within this inclusive range, {@code false} otherwise
+ *
+ * @throws NullPointerException if {@code range} was {@code null}
+ */
+ public boolean contains(Range<T> range) {
+ checkNotNull(range, "value must not be null");
+
+ boolean gteLower = range.mLower.compareTo(mLower) >= 0;
+ boolean lteUpper = range.mUpper.compareTo(mUpper) <= 0;
+
+ return gteLower && lteUpper;
+ }
+
+ /**
* Compare two ranges for equality.
*
* <p>A range is considered equal if and only if both the lower and upper endpoints
@@ -140,6 +160,182 @@
}
/**
+ * Clamps {@code value} to this range.
+ *
+ * <p>If the value is within this range, it is returned. Otherwise, if it
+ * is {@code <} than the lower endpoint, the lower endpoint is returned,
+ * else the upper endpoint is returned. Comparisons are performed using the
+ * {@link Comparable} interface.</p>
+ *
+ * @param value a non-{@code null} {@code T} reference
+ * @return {@code value} clamped to this range.
+ */
+ public T clamp(T value) {
+ checkNotNull(value, "value must not be null");
+
+ if (value.compareTo(mLower) < 0) {
+ return mLower;
+ } else if (value.compareTo(mUpper) > 0) {
+ return mUpper;
+ } else {
+ return value;
+ }
+ }
+
+ /**
+ * Returns the intersection of this range and another {@code range}.
+ * <p>
+ * E.g. if a {@code <} b {@code <} c {@code <} d, the
+ * intersection of [a, c] and [b, d] ranges is [b, c].
+ * As the endpoints are object references, there is no guarantee
+ * which specific endpoint reference is used from the input ranges:</p>
+ * <p>
+ * E.g. if a {@code ==} a' {@code <} b {@code <} c, the
+ * intersection of [a, b] and [a', c] ranges could be either
+ * [a, b] or ['a, b], where [a, b] could be either the exact
+ * input range, or a newly created range with the same endpoints.</p>
+ *
+ * @param range a non-{@code null} {@code Range<T>} reference
+ * @return the intersection of this range and the other range.
+ *
+ * @throws NullPointerException if {@code range} was {@code null}
+ * @throws IllegalArgumentException if the ranges are disjoint.
+ */
+ public Range<T> intersect(Range<T> range) {
+ checkNotNull(range, "range must not be null");
+
+ int cmpLower = range.mLower.compareTo(mLower);
+ int cmpUpper = range.mUpper.compareTo(mUpper);
+
+ if (cmpLower <= 0 && cmpUpper >= 0) {
+ // range includes this
+ return this;
+ } else if (cmpLower >= 0 && cmpUpper <= 0) {
+ // this inludes range
+ return range;
+ } else {
+ return Range.create(
+ cmpLower <= 0 ? mLower : range.mLower,
+ cmpUpper >= 0 ? mUpper : range.mUpper);
+ }
+ }
+
+ /**
+ * Returns the intersection of this range and the inclusive range
+ * specified by {@code [lower, upper]}.
+ * <p>
+ * See {@link #intersect(Range)} for more details.</p>
+ *
+ * @param lower a non-{@code null} {@code T} reference
+ * @param upper a non-{@code null} {@code T} reference
+ * @return the intersection of this range and the other range
+ *
+ * @throws NullPointerException if {@code lower} or {@code upper} was {@code null}
+ * @throws IllegalArgumentException if the ranges are disjoint.
+ */
+ public Range<T> intersect(T lower, T upper) {
+ checkNotNull(lower, "lower must not be null");
+ checkNotNull(upper, "upper must not be null");
+
+ int cmpLower = lower.compareTo(mLower);
+ int cmpUpper = upper.compareTo(mUpper);
+
+ if (cmpLower <= 0 && cmpUpper >= 0) {
+ // [lower, upper] includes this
+ return this;
+ } else {
+ return Range.create(
+ cmpLower <= 0 ? mLower : lower,
+ cmpUpper >= 0 ? mUpper : upper);
+ }
+ }
+
+ /**
+ * Returns the smallest range that includes this range and
+ * another {@code range}.
+ * <p>
+ * E.g. if a {@code <} b {@code <} c {@code <} d, the
+ * extension of [a, c] and [b, d] ranges is [a, d].
+ * As the endpoints are object references, there is no guarantee
+ * which specific endpoint reference is used from the input ranges:</p>
+ * <p>
+ * E.g. if a {@code ==} a' {@code <} b {@code <} c, the
+ * extension of [a, b] and [a', c] ranges could be either
+ * [a, c] or ['a, c], where ['a, c] could be either the exact
+ * input range, or a newly created range with the same endpoints.</p>
+ *
+ * @param range a non-{@code null} {@code Range<T>} reference
+ * @return the extension of this range and the other range.
+ *
+ * @throws NullPointerException if {@code range} was {@code null}
+ */
+ public Range<T> extend(Range<T> range) {
+ checkNotNull(range, "range must not be null");
+
+ int cmpLower = range.mLower.compareTo(mLower);
+ int cmpUpper = range.mUpper.compareTo(mUpper);
+
+ if (cmpLower <= 0 && cmpUpper >= 0) {
+ // other includes this
+ return range;
+ } else if (cmpLower >= 0 && cmpUpper <= 0) {
+ // this inludes other
+ return this;
+ } else {
+ return Range.create(
+ cmpLower >= 0 ? mLower : range.mLower,
+ cmpUpper <= 0 ? mUpper : range.mUpper);
+ }
+ }
+
+ /**
+ * Returns the smallest range that includes this range and
+ * the inclusive range specified by {@code [lower, upper]}.
+ * <p>
+ * See {@link #extend(Range)} for more details.</p>
+ *
+ * @param lower a non-{@code null} {@code T} reference
+ * @param upper a non-{@code null} {@code T} reference
+ * @return the extension of this range and the other range.
+ *
+ * @throws NullPointerException if {@code lower} or {@code
+ * upper} was {@code null}
+ */
+ public Range<T> extend(T lower, T upper) {
+ checkNotNull(lower, "lower must not be null");
+ checkNotNull(upper, "upper must not be null");
+
+ int cmpLower = lower.compareTo(mLower);
+ int cmpUpper = upper.compareTo(mUpper);
+
+ if (cmpLower >= 0 && cmpUpper <= 0) {
+ // this inludes other
+ return this;
+ } else {
+ return Range.create(
+ cmpLower >= 0 ? mLower : lower,
+ cmpUpper <= 0 ? mUpper : upper);
+ }
+ }
+
+ /**
+ * Returns the smallest range that includes this range and
+ * the {@code value}.
+ * <p>
+ * See {@link #extend(Range)} for more details, as this method is
+ * equivalent to {@code extend(Range.create(value, value))}.</p>
+ *
+ * @param value a non-{@code null} {@code T} reference
+ * @return the extension of this range and the value.
+ *
+ * @throws NullPointerException if {@code value} was {@code null}
+ */
+ public Range<T> extend(T value) {
+ checkNotNull(value, "value must not be null");
+ return extend(value, value);
+ }
+
+ /**
* Return the range as a string representation {@code "[lower, upper]"}.
*
* @return string representation of the range
diff --git a/core/java/android/util/Rational.java b/core/java/android/util/Rational.java
index 9952859..80d26d9 100644
--- a/core/java/android/util/Rational.java
+++ b/core/java/android/util/Rational.java
@@ -536,8 +536,67 @@
} else { // finite value
if (gcd(mNumerator, mDenominator) > 1) {
throw new InvalidObjectException(
- "Rational must be deserialized from a reduced form for finite values");
+ "Rational must be deserialized from a reduced form for finite values");
}
}
}
+
+ private static NumberFormatException invalidRational(String s) {
+ throw new NumberFormatException("Invalid Rational: \"" + s + "\"");
+ }
+
+ /**
+ * Parses the specified string as a rational value.
+ * <p>The ASCII characters {@code \}{@code u003a} (':') and
+ * {@code \}{@code u002f} ('/') are recognized as separators between
+ * the numerator and denumerator.</p>
+ * <p>
+ * For any {@code Rational r}: {@code Rational.parseRational(r.toString()).equals(r)}.
+ * However, the method also handles rational numbers expressed in the
+ * following forms:</p>
+ * <p>
+ * "<i>num</i>{@code /}<i>den</i>" or
+ * "<i>num</i>{@code :}<i>den</i>" {@code => new Rational(num, den);},
+ * where <i>num</i> and <i>den</i> are string integers potentially
+ * containing a sign, such as "-10", "+7" or "5".</p>
+ *
+ * <pre>{@code
+ * Rational.parseRational("3:+6").equals(new Rational(1, 2)) == true
+ * Rational.parseRational("-3/-6").equals(new Rational(1, 2)) == true
+ * Rational.parseRational("4.56") => throws NumberFormatException
+ * }</pre>
+ *
+ * @param string the string representation of a rational value.
+ * @return the rational value represented by {@code string}.
+ *
+ * @throws NumberFormatException if {@code string} cannot be parsed
+ * as a rational value.
+ * @throws NullPointerException if {@code string} was {@code null}
+ */
+ public static Rational parseRational(String string)
+ throws NumberFormatException {
+ checkNotNull(string, "string must not be null");
+
+ if (string.equals("NaN")) {
+ return NaN;
+ } else if (string.equals("Infinity")) {
+ return POSITIVE_INFINITY;
+ } else if (string.equals("-Infinity")) {
+ return NEGATIVE_INFINITY;
+ }
+
+ int sep_ix = string.indexOf(':');
+ if (sep_ix < 0) {
+ sep_ix = string.indexOf('/');
+ }
+ if (sep_ix < 0) {
+ throw invalidRational(string);
+ }
+ try {
+ return new Rational(Integer.parseInt(string.substring(0, sep_ix)),
+ Integer.parseInt(string.substring(sep_ix + 1)));
+ } catch (NumberFormatException e) {
+ throw invalidRational(string);
+ }
+ }
}
diff --git a/core/java/android/util/Size.java b/core/java/android/util/Size.java
index ba1a35f..d58f778 100644
--- a/core/java/android/util/Size.java
+++ b/core/java/android/util/Size.java
@@ -16,6 +16,8 @@
package android.util;
+import static com.android.internal.util.Preconditions.*;
+
/**
* Immutable class for describing width and height dimensions in pixels.
*/
@@ -84,6 +86,58 @@
return mWidth + "x" + mHeight;
}
+ private static NumberFormatException invalidSize(String s) {
+ throw new NumberFormatException("Invalid Size: \"" + s + "\"");
+ }
+
+ /**
+ * Parses the specified string as a size value.
+ * <p>
+ * The ASCII characters {@code \}{@code u002a} ('*') and
+ * {@code \}{@code u0078} ('x') are recognized as separators between
+ * the width and height.</p>
+ * <p>
+ * For any {@code Size s}: {@code Size.parseSize(s.toString()).equals(s)}.
+ * However, the method also handles sizes expressed in the
+ * following forms:</p>
+ * <p>
+ * "<i>width</i>{@code x}<i>height</i>" or
+ * "<i>width</i>{@code *}<i>height</i>" {@code => new Size(width, height)},
+ * where <i>width</i> and <i>height</i> are string integers potentially
+ * containing a sign, such as "-10", "+7" or "5".</p>
+ *
+ * <pre>{@code
+ * Size.parseSize("3*+6").equals(new Size(3, 6)) == true
+ * Size.parseSize("-3x-6").equals(new Size(-3, -6)) == true
+ * Size.parseSize("4 by 3") => throws NumberFormatException
+ * }</pre>
+ *
+ * @param string the string representation of a size value.
+ * @return the size value represented by {@code string}.
+ *
+ * @throws NumberFormatException if {@code string} cannot be parsed
+ * as a size value.
+ * @throws NullPointerException if {@code string} was {@code null}
+ */
+ public static Size parseSize(String string)
+ throws NumberFormatException {
+ checkNotNull(string, "string must not be null");
+
+ int sep_ix = string.indexOf('*');
+ if (sep_ix < 0) {
+ sep_ix = string.indexOf('x');
+ }
+ if (sep_ix < 0) {
+ throw invalidSize(string);
+ }
+ try {
+ return new Size(Integer.parseInt(string.substring(0, sep_ix)),
+ Integer.parseInt(string.substring(sep_ix + 1)));
+ } catch (NumberFormatException e) {
+ throw invalidSize(string);
+ }
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index be677ea..f2f363a 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -248,24 +248,8 @@
abstract void detachSurfaceTexture(long hardwareLayer);
/**
- * Setup the hardware renderer for drawing. This is called whenever the size
- * of the target surface changes or when the surface is first created.
- *
- * @param width Width of the drawing surface.
- * @param height Height of the drawing surface.
- * @param surfaceInsets Insets between the drawing surface and actual
- * surface bounds.
- * @param lightX X position of the shadow casting light
- * @param lightY Y position of the shadow casting light
- * @param lightZ Z position of the shadow casting light
- * @param lightRadius radius of the shadow casting light
- */
- abstract void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY,
- float lightZ, float lightRadius);
-
- /**
* Gets the current width of the surface. This is the width that the surface
- * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}.
+ * was last set to in a call to {@link #setup(int, int, Rect)}.
*
* @return the current width of the surface
*/
@@ -273,7 +257,7 @@
/**
* Gets the current height of the surface. This is the height that the surface
- * was last set to in a call to {@link #setup(int, int, Rect, float, float, float, float)}.
+ * was last set to in a call to {@link #setup(int, int, Rect)}.
*
* @return the current width of the surface
*/
@@ -371,19 +355,18 @@
* @param width The width of the drawing surface.
* @param height The height of the drawing surface.
* @param surface The surface to hardware accelerate
- * @param metrics The display metrics used to draw the output.
* @param surfaceInsets The drawing surface insets to apply
*
* @return true if the surface was initialized, false otherwise. Returning
* false might mean that the surface was already initialized.
*/
- boolean initializeIfNeeded(int width, int height, Surface surface, Rect surfaceInsets, DisplayMetrics metrics)
+ boolean initializeIfNeeded(int width, int height, Surface surface, Rect surfaceInsets)
throws OutOfResourcesException {
if (isRequested()) {
// We lost the gl context, so recreate it.
if (!isEnabled()) {
if (initialize(surface)) {
- setup(width, height, surfaceInsets, metrics);
+ setup(width, height, surfaceInsets);
return true;
}
}
@@ -391,13 +374,14 @@
return false;
}
- void setup(int width, int height, Rect surfaceInsets, DisplayMetrics metrics) {
- float lightX = width / 2.0f;
- float lightY = -400 * metrics.density;
- float lightZ = 800 * metrics.density;
- float lightRadius = 800 * metrics.density;
- setup(width, height, surfaceInsets, lightX, lightY, lightZ, lightRadius);
- }
+ /**
+ * Sets up the renderer for drawing.
+ *
+ * @param width The width of the drawing surface.
+ * @param height The height of the drawing surface.
+ * @param surfaceInsets The drawing surface insets to apply
+ */
+ abstract void setup(int width, int height, Rect surfaceInsets);
/**
* Optional, sets the name of the renderer. Useful for debugging purposes.
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index acb2fe4..fb8ce15 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -16,8 +16,11 @@
package android.view;
+import com.android.internal.R;
+
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -78,6 +81,13 @@
// applied as translation when updating the root render node.
private int mInsetTop, mInsetLeft;
+ // Light and shadow properties specified by the theme.
+ private final float mLightY;
+ private final float mLightZ;
+ private final float mLightRadius;
+ private final float mAmbientShadowAlpha;
+ private final float mSpotShadowAlpha;
+
private long mNativeProxy;
private boolean mInitialized = false;
private RenderNode mRootNode;
@@ -85,6 +95,15 @@
private boolean mProfilingEnabled;
ThreadedRenderer(Context context, boolean translucent) {
+ final TypedArray a = context.obtainStyledAttributes(
+ null, R.styleable.Lighting, R.attr.lightingStyle, 0);
+ mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
+ mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
+ mLightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
+ mAmbientShadowAlpha = a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0);
+ mSpotShadowAlpha = a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0);
+ a.recycle();
+
long rootNodePtr = nCreateRootRenderNode();
mRootNode = RenderNode.adopt(rootNodePtr);
mRootNode.setClipToBounds(false);
@@ -164,8 +183,8 @@
}
@Override
- void setup(int width, int height, Rect surfaceInsets, float lightX, float lightY, float lightZ,
- float lightRadius) {
+ void setup(int width, int height, Rect surfaceInsets) {
+ final float lightX = width / 2.0f;
mWidth = width;
mHeight = height;
if (surfaceInsets != null) {
@@ -180,7 +199,7 @@
mSurfaceHeight = height;
}
mRootNode.setLeftTopRightBottom(-mInsetLeft, -mInsetTop, mSurfaceWidth, mSurfaceHeight);
- nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, lightY, lightZ, lightRadius);
+ nSetup(mNativeProxy, mSurfaceWidth, mSurfaceHeight, lightX, mLightY, mLightZ, mLightRadius);
}
@Override
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ec39ee7..a09a061 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2374,30 +2374,27 @@
static final int PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT = 0x8;
/**
- * Flag indicating that an overridden method correctly called down to
+ * Flag indicating that an overridden method correctly called down to
* the superclass implementation as required by the API spec.
*/
static final int PFLAG3_CALLED_SUPER = 0x10;
- @Deprecated
- static final int PFLAG3_OUTLINE_DEFINED = 0x20;
-
/**
* Flag indicating that we're in the process of applying window insets.
*/
- static final int PFLAG3_APPLYING_INSETS = 0x40;
+ static final int PFLAG3_APPLYING_INSETS = 0x20;
/**
* Flag indicating that we're in the process of fitting system windows using the old method.
*/
- static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x80;
+ static final int PFLAG3_FITTING_SYSTEM_WINDOWS = 0x40;
/**
* Flag indicating that nested scrolling is enabled for this view.
* The view will optionally cooperate with views up its parent chain to allow for
* integrated nested scrolling along the same axis.
*/
- static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x200;
+ static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x80;
/* End of masks for mPrivateFlags3 */
@@ -3273,8 +3270,6 @@
private int[] mDrawableState = null;
- @Deprecated
- private Outline mOutline;
ViewOutlineProvider mOutlineProvider = ViewOutlineProvider.BACKGROUND;
/**
@@ -10751,23 +10746,9 @@
}
}
+ /** Deprecated, pending removal */
@Deprecated
- public void setOutline(@Nullable Outline outline) {
- mPrivateFlags3 |= PFLAG3_OUTLINE_DEFINED;
-
- if (outline == null || outline.isEmpty()) {
- if (mOutline != null) {
- mOutline.setEmpty();
- }
- } else {
- // always copy the path since caller may reuse
- if (mOutline == null) {
- mOutline = new Outline();
- }
- mOutline.set(outline);
- }
- mRenderNode.setOutline(mOutline);
- }
+ public void setOutline(@Nullable Outline outline) {}
/**
* Returns whether the Outline should be used to clip the contents of the View.
@@ -10836,12 +10817,6 @@
* @see #setOutlineProvider(ViewOutlineProvider)
*/
public void invalidateOutline() {
- if ((mPrivateFlags3 & PFLAG3_OUTLINE_DEFINED) != 0) {
- // TODO: remove this when removing old outline code
- // setOutline() was called to manually set outline, ignore provider
- return;
- }
-
// Unattached views ignore this signal, and outline is recomputed in onAttachedToWindow()
if (mAttachInfo == null) return;
@@ -14948,6 +14923,7 @@
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
+ p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
@@ -14956,6 +14932,7 @@
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
+ p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
@@ -14964,6 +14941,7 @@
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
+ p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
@@ -14972,6 +14950,7 @@
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
+ p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
@@ -17493,7 +17472,8 @@
getLocationInWindow(location);
region.op(location[0], location[1], location[0] + mRight - mLeft,
location[1] + mBottom - mTop, Region.Op.DIFFERENCE);
- } else if ((pflags & PFLAG_ONLY_DRAWS_BACKGROUND) != 0 && mBackground != null) {
+ } else if ((pflags & PFLAG_ONLY_DRAWS_BACKGROUND) != 0 && mBackground != null &&
+ mBackground.getOpacity() != PixelFormat.TRANSPARENT) {
// The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable
// exists, so we remove the background drawable's non-transparent
// parts from this transparent region.
@@ -19388,14 +19368,6 @@
}
/**
- * To be removed before L release.
- * @hide
- */
- public final void setViewName(String transitionName) {
- setTransitionName(transitionName);
- }
-
- /**
* Returns the name of the View to be used to identify Views in Transitions.
* Names should be unique in the View hierarchy.
*
@@ -19409,12 +19381,6 @@
}
/**
- * To be removed before L release.
- * @hide
- */
- public String getViewName() { return getTransitionName(); }
-
- /**
* Interface definition for a callback to be invoked when a hardware key event is
* dispatched to this view. The callback will be invoked before the key event is
* given to the view. This is only useful for hardware keyboards; a software input
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index edc9971..ac70066 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3333,6 +3333,17 @@
}
/**
+ * Check if this ViewGroup is configured to clip child views to its padding.
+ *
+ * @return true if this ViewGroup clips children to its padding, false otherwise
+ *
+ * @attr ref android.R.styleable#ViewGroup_clipToPadding
+ */
+ public boolean getClipToPadding() {
+ return hasBooleanFlag(FLAG_CLIP_TO_PADDING);
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9405299..7fab808 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1714,8 +1714,7 @@
mWidth != mAttachInfo.mHardwareRenderer.getWidth() ||
mHeight != mAttachInfo.mHardwareRenderer.getHeight()) {
final Rect shadowInsets = params != null ? params.shadowInsets : null;
- mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, shadowInsets,
- mAttachInfo.mRootView.getResources().getDisplayMetrics());
+ mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, shadowInsets);
if (!hwInitialized) {
mAttachInfo.mHardwareRenderer.invalidate(mSurface);
mFullRedrawNeeded = true;
@@ -2459,8 +2458,7 @@
try {
attachInfo.mHardwareRenderer.initializeIfNeeded(
- mWidth, mHeight, mSurface, surfaceInsets,
- attachInfo.mRootView.getResources().getDisplayMetrics());
+ mWidth, mHeight, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
@@ -3159,8 +3157,7 @@
final WindowManager.LayoutParams lp = mWindowAttributes;
final Rect surfaceInsets = lp != null ? lp.shadowInsets : null;
mAttachInfo.mHardwareRenderer.initializeIfNeeded(
- mWidth, mHeight, mSurface, surfaceInsets,
- mAttachInfo.mRootView.getResources().getDisplayMetrics());
+ mWidth, mHeight, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
Log.e(TAG, "OutOfResourcesException locking surface", e);
try {
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index e1f40b7..338f3d2 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -429,6 +429,21 @@
}
/**
+ * The default implementation is responsible for handling
+ * {@link CursorAnchorInfoRequest#TYPE_CURSOR_RECT}. In fact, for derived classes, calling
+ * {@code super.requestCursorAnchorInfo(request)} is the only way to handle
+ * {@link CursorAnchorInfoRequest#TYPE_CURSOR_RECT}.
+ */
+ public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) {
+ if (request != null && mIMM != null &&
+ request.getRequestType() == CursorAnchorInfoRequest.TYPE_CURSOR_RECT) {
+ mIMM.setCursorRectMonitorMode(request.getRequestFlags());
+ return CursorAnchorInfoRequest.RESULT_SCHEDULED;
+ }
+ return CursorAnchorInfoRequest.RESULT_NOT_HANDLED;
+ }
+
+ /**
* The default implementation places the given text into the editable,
* replacing any existing composing text. The new text is marked as
* in a composing state with the composing style.
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index 04d875d..730b7f6 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -257,11 +257,12 @@
/**
* Sets the text range of the composing text. Calling this can be skipped if there is
* no composing text.
- * @param index index where the composing text starts.
+ * @param composingTextStart index where the composing text starts.
* @param composingText the entire composing text.
*/
- public Builder setComposingText(final int index, final CharSequence composingText) {
- mComposingTextStart = index;
+ public Builder setComposingText(final int composingTextStart,
+ final CharSequence composingText) {
+ mComposingTextStart = composingTextStart;
if (composingText == null) {
mComposingText = null;
} else {
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.aidl
similarity index 89%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to core/java/android/view/inputmethod/CursorAnchorInfoRequest.aidl
index 3da1321..41ef7cc6 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.bluetooth.le;
+package android.view.inputmethod;
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable CursorAnchorInfoRequest;
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfoRequest.java b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.java
new file mode 100644
index 0000000..e4c94f2
--- /dev/null
+++ b/core/java/android/view/inputmethod/CursorAnchorInfoRequest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.view.inputmethod;
+
+import android.inputmethodservice.InputMethodService;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.View;
+
+/**
+ * Used to enable or disable event notification for
+ * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}. This class is also used to
+ * enable {@link InputMethodService#onUpdateCursor(android.graphics.Rect)} for existing editors
+ * that have not supported {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}.
+ */
+public final class CursorAnchorInfoRequest implements Parcelable {
+ private final int mRequestType;
+ private final int mRequestFlags;
+
+ /**
+ * Not handled by the editor.
+ */
+ public static final int RESULT_NOT_HANDLED = 0x00;
+ /**
+ * Request is scheduled in the editor task queue.
+ */
+ public static final int RESULT_SCHEDULED = 0x01;
+
+ /**
+ * The request is for {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)}.
+ * This mechanism is powerful enough to retrieve fine-grained positional information of
+ * characters in the editor.
+ */
+ public static final int TYPE_CURSOR_ANCHOR_INFO = 0x01;
+ /**
+ * The editor is requested to call
+ * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}
+ * whenever cursor/anchor position is changed. To disable monitoring, call
+ * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} again with
+ * {@link #TYPE_CURSOR_ANCHOR_INFO} and this flag off.
+ * <p>
+ * This flag can be used together with {@link #FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE}.
+ * </p>
+ */
+ public static final int FLAG_CURSOR_ANCHOR_INFO_MONITOR = 0x01;
+ /**
+ * The editor is requested to call
+ * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} at
+ * once, as soon as possible, regardless of cursor/anchor position changes. This flag can be
+ * used together with {@link #FLAG_CURSOR_ANCHOR_INFO_MONITOR}.
+ */
+ public static final int FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE = 0x02;
+
+ /**
+ * The request is for {@link InputMethodService#onUpdateCursor(android.graphics.Rect)}. This
+ * mechanism has been available since API Level 3 (CUPCAKE) but only the cursor rectangle can
+ * be retrieved with this mechanism.
+ */
+ public static final int TYPE_CURSOR_RECT = 0x02;
+ /**
+ * The editor is requested to call
+ * {@link InputMethodManager#updateCursor(android.view.View, int, int, int, int)}
+ * whenever the cursor position is changed. To disable monitoring, call
+ * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} again with
+ * {@link #TYPE_CURSOR_RECT} and this flag off.
+ * <p>
+ * This flag can be used together with {@link #FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES}.
+ * </p>
+ */
+ public static final int FLAG_CURSOR_RECT_MONITOR = 0x01;
+ /**
+ * {@link InputMethodManager#updateCursor(android.view.View, int, int, int, int)} should be
+ * called back in screen coordinates. To receive cursor position in local coordinates, call
+ * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} again with
+ * {@link #TYPE_CURSOR_RECT} and this flag off.
+ */
+ public static final int FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES = 0x02;
+ /**
+ * {@link InputMethodManager#updateCursor(android.view.View, int, int, int, int)} should be
+ * called back in screen coordinates after coordinate conversion with {@link View#getMatrix()}.
+ * To disable coordinate conversion with {@link View#getMatrix()} again, call
+ * {@link InputConnection#requestCursorAnchorInfo(CursorAnchorInfoRequest)} with
+ * {@link #TYPE_CURSOR_RECT} and this flag off.
+ *
+ * <p>
+ * The flag is ignored if {@link #FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES} is off.
+ * </p>
+ */
+ public static final int FLAG_CURSOR_RECT_WITH_VIEW_MATRIX = 0x04;
+
+ /**
+ * Constructs the object with request type and type-specific flags.
+ *
+ * @param requestType the type of this request. Currently {@link #TYPE_CURSOR_ANCHOR_INFO} or
+ * {@link #TYPE_CURSOR_RECT} is supported.
+ * @param requestFlags the flags for the given request type.
+ */
+ public CursorAnchorInfoRequest(int requestType, int requestFlags) {
+ mRequestType = requestType;
+ mRequestFlags = requestFlags;
+ }
+
+ /**
+ * Used to make this class parcelable.
+ *
+ * @param source the parcel from which the object is unmarshalled.
+ */
+ public CursorAnchorInfoRequest(Parcel source) {
+ mRequestType = source.readInt();
+ mRequestFlags = source.readInt();
+ }
+
+ /**
+ * @return the type of this request.
+ */
+ public int getRequestType() {
+ return mRequestType;
+ }
+
+ /**
+ * @return the flags that are specific to the type of this request.
+ */
+ public int getRequestFlags() {
+ return mRequestFlags;
+ }
+
+ /**
+ * Used to package this object into a {@link Parcel}.
+ *
+ * @param dest The {@link Parcel} to be written.
+ * @param flags The flags used for parceling.
+ */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mRequestType);
+ dest.writeInt(mRequestFlags);
+ }
+
+ @Override
+ public int hashCode(){
+ return mRequestType * 31 + mRequestFlags;
+ }
+
+ @Override
+ public boolean equals(Object obj){
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof CursorAnchorInfoRequest)) {
+ return false;
+ }
+ final CursorAnchorInfoRequest that = (CursorAnchorInfoRequest) obj;
+ if (hashCode() != that.hashCode()) {
+ return false;
+ }
+ return mRequestType != that.mRequestType && mRequestFlags == that.mRequestFlags;
+ }
+
+ @Override
+ public String toString() {
+ return "CursorAnchorInfoRequest{mRequestType=" + mRequestType
+ + " mRequestFlags=" + mRequestFlags
+ + "}";
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<CursorAnchorInfoRequest> CREATOR =
+ new Parcelable.Creator<CursorAnchorInfoRequest>() {
+ @Override
+ public CursorAnchorInfoRequest createFromParcel(Parcel source) {
+ return new CursorAnchorInfoRequest(source);
+ }
+
+ @Override
+ public CursorAnchorInfoRequest[] newArray(int size) {
+ return new CursorAnchorInfoRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 3537aec..dff91dc 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -723,4 +723,15 @@
* valid.
*/
public boolean performPrivateCommand(String action, Bundle data);
+
+ /**
+ * Called by the IME to ask the editor for calling back
+ * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)} to
+ * notify cursor/anchor locations.
+ *
+ * @param request the details of the request.
+ * @return a result code that depends on {@link CursorAnchorInfoRequest#getRequestType()}. See
+ * {@link CursorAnchorInfoRequest} for details.
+ */
+ public int requestCursorAnchorInfo(CursorAnchorInfoRequest request);
}
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index a48473e..c831d7c 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -125,4 +125,8 @@
public boolean performPrivateCommand(String action, Bundle data) {
return mTarget.performPrivateCommand(action, data);
}
-}
+
+ public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) {
+ return mTarget.requestCursorAnchorInfo(request);
+ }
+ }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ace8808..623b5f9 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -25,8 +25,9 @@
import com.android.internal.view.InputBindResult;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.Rect;
-import android.inputmethodservice.InputMethodService;
+import android.graphics.RectF;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -312,8 +313,9 @@
CompletionInfo[] mCompletions;
// Cursor position on the screen.
- Rect mTmpCursorRect = new Rect();
+ Rect mNextCursorRect = new Rect();
Rect mCursorRect = new Rect();
+ RectF mTempRectF = new RectF();
int mCursorSelStart;
int mCursorSelEnd;
int mCursorCandStart;
@@ -348,8 +350,13 @@
*/
private final int[] mViewTopLeft = new int[2];
+ /**
+ * The matrix to convert the view location into screen coordinates in {@link #updateCursor}.
+ */
+ private final Matrix mViewToScreenMatrix = new Matrix();
+
// -----------------------------------------------------------
-
+
/**
* Sequence number of this binding, as returned by the server.
*/
@@ -365,10 +372,28 @@
InputChannel mCurChannel;
ImeInputEventSender mCurSender;
+ private static final int CURSOR_RECT_MONITOR_MODE_NONE = 0x0;
+
+ private static final int CURSOR_RECT_MONITOR_FLAG_MASK =
+ CursorAnchorInfoRequest.FLAG_CURSOR_RECT_MONITOR |
+ CursorAnchorInfoRequest.FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES |
+ CursorAnchorInfoRequest.FLAG_CURSOR_RECT_WITH_VIEW_MATRIX;
+
+ private static final int CURSOR_ANCHOR_INFO_MONITOR_MODE_NONE = 0x0;
+
+ private static final int CURSOR_ANCHOR_INFO_MONITOR_FLAG_MASK =
+ CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_MONITOR |
+ CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE;
+
/**
- * The current cursor/anchor monitor mode.
+ * The monitor mode for {@link #updateCursor(View, int, int, int, int)}.
*/
- int mCursorAnchorMonitorMode = InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_NONE;
+ private int mCursorRectMonitorMode = CURSOR_RECT_MONITOR_MODE_NONE;
+
+ /**
+ * The monitor mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
+ */
+ private int mCursorAnchorInfoMonitorMode = CURSOR_ANCHOR_INFO_MONITOR_MODE_NONE;
final Pool<PendingEvent> mPendingEventPool = new SimplePool<PendingEvent>(20);
final SparseArray<PendingEvent> mPendingEvents = new SparseArray<PendingEvent>(20);
@@ -382,7 +407,6 @@
static final int MSG_SEND_INPUT_EVENT = 5;
static final int MSG_TIMEOUT_INPUT_EVENT = 6;
static final int MSG_FLUSH_INPUT_EVENT = 7;
- static final int MSG_SET_CURSOR_ANCHOR_MONITOR_MODE = 8;
static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 9;
class H extends Handler {
@@ -422,6 +446,9 @@
return;
}
+ mCursorAnchorInfoMonitorMode = CURSOR_ANCHOR_INFO_MONITOR_MODE_NONE;
+ mCursorRectMonitorMode = CURSOR_RECT_MONITOR_MODE_NONE;
+
setInputChannelLocked(res.channel);
mCurMethod = res.method;
mCurId = res.id;
@@ -514,15 +541,6 @@
finishedInputEvent(msg.arg1, false, false);
return;
}
- case MSG_SET_CURSOR_ANCHOR_MONITOR_MODE: {
- synchronized (mH) {
- mCursorAnchorMonitorMode = msg.arg1;
- // Clear the cache.
- mCursorRect.setEmpty();
- mCursorAnchorInfo = null;
- }
- return;
- }
case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
synchronized (mH) {
mNextUserActionNotificationSequenceNumber = msg.arg1;
@@ -594,11 +612,6 @@
}
@Override
- public void setCursorAnchorMonitorMode(int monitorMode) {
- mH.sendMessage(mH.obtainMessage(MSG_SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, 0));
- }
-
- @Override
public void setUserActionNotificationSequenceNumber(int sequenceNumber) {
mH.sendMessage(mH.obtainMessage(MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
sequenceNumber, 0));
@@ -1535,37 +1548,45 @@
return false;
}
synchronized (mH) {
- return (mCursorAnchorMonitorMode &
- InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT) != 0;
+ return (mCursorRectMonitorMode & CursorAnchorInfoRequest.FLAG_CURSOR_RECT_MONITOR) != 0;
}
}
/**
- * Returns true if the current input method wants to receive the cursor rectangle in
- * screen coordinates rather than local coordinates in the attached view.
+ * Updates the result of {@link #isWatchingCursor(View)}.
*
* @hide
*/
- public boolean usesScreenCoordinatesForCursorLocked() {
- // {@link InputMethodService#CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT} also means
- // that {@link InputMethodService#onUpdateCursor} should provide the cursor rectangle
- // in screen coordinates rather than local coordinates.
- return (mCursorAnchorMonitorMode &
- InputMethodService.CURSOR_ANCHOR_MONITOR_MODE_CURSOR_RECT) != 0;
+ public void setCursorRectMonitorMode(int flags) {
+ synchronized (mH) {
+ mCursorRectMonitorMode = (CURSOR_RECT_MONITOR_FLAG_MASK & flags);
+ }
}
/**
- * Set cursor/anchor monitor mode via {@link com.android.server.InputMethodManagerService}.
- * This is an internal method for {@link android.inputmethodservice.InputMethodService} and
- * should never be used from IMEs and applications.
+ * Returns true if the current input method wants to be notified when cursor/anchor location
+ * is changed.
*
* @hide
*/
- public void setCursorAnchorMonitorMode(IBinder imeToken, int monitorMode) {
- try {
- mService.setCursorAnchorMonitorMode(imeToken, monitorMode);
- } catch (RemoteException e) {
- throw new RuntimeException(e);
+ public boolean isCursorAnchorInfoEnabled() {
+ synchronized (mH) {
+ final boolean isImmediate = (mCursorAnchorInfoMonitorMode &
+ CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE) != 0;
+ final boolean isMonitoring = (mCursorAnchorInfoMonitorMode &
+ CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_MONITOR) != 0;
+ return isImmediate || isMonitoring;
+ }
+ }
+
+ /**
+ * Updates the result of {@link #isWatchingCursor(View)}.
+ *
+ * @hide
+ */
+ public void setCursorAnchorInfoMonitorMode(int flags) {
+ synchronized (mH) {
+ mCursorAnchorInfoMonitorMode = (CURSOR_ANCHOR_INFO_MONITOR_FLAG_MASK & flags);
}
}
@@ -1581,16 +1602,32 @@
return;
}
if (DEBUG) Log.d(TAG, "updateCursor");
- mTmpCursorRect.set(left, top, right, bottom);
- if (!Objects.equals(mCursorRect, mTmpCursorRect)) {
+ final boolean usesScreenCoordinates = (mCursorRectMonitorMode &
+ CursorAnchorInfoRequest.FLAG_CURSOR_RECT_IN_SCREEN_COORDINATES) != 0;
+ if (usesScreenCoordinates) {
+ view.getLocationOnScreen(mViewTopLeft);
+ final Matrix viewMatrix = view.getMatrix();
+ final boolean usesViewMatrix = (viewMatrix != null) && ((mCursorRectMonitorMode &
+ CursorAnchorInfoRequest.FLAG_CURSOR_RECT_WITH_VIEW_MATRIX) != 0);
+ if (usesViewMatrix) {
+ mTempRectF.set(left, top, right, bottom);
+ mViewToScreenMatrix.set(viewMatrix);
+ mViewToScreenMatrix.postTranslate(mViewTopLeft[0], mViewTopLeft[1]);
+ mViewToScreenMatrix.mapRect(mTempRectF);
+ mNextCursorRect.set((int)mTempRectF.left, (int)mTempRectF.top,
+ (int)mTempRectF.right, (int)mTempRectF.bottom);
+ } else {
+ mNextCursorRect.set(left + mViewTopLeft[0], top + mViewTopLeft[1],
+ right + mViewTopLeft[0], bottom + mViewTopLeft[1]);
+ }
+ } else {
+ mNextCursorRect.set(left, top, right, bottom);
+ }
+ if (!Objects.equals(mCursorRect, mNextCursorRect)) {
+ if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mNextCursorRect);
try {
- if (DEBUG) Log.v(TAG, "CURSOR CHANGE: " + mCurMethod);
- mCursorRect.set(mTmpCursorRect);
- if (usesScreenCoordinatesForCursorLocked()) {
- view.getLocationOnScreen(mViewTopLeft);
- mTmpCursorRect.offset(mViewTopLeft[0], mViewTopLeft[1]);
- }
- mCurMethod.updateCursor(mTmpCursorRect);
+ mCurMethod.updateCursor(mNextCursorRect);
+ mCursorRect.set(mNextCursorRect);
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
@@ -1613,7 +1650,11 @@
|| mCurrentTextBoxAttribute == null || mCurMethod == null) {
return;
}
- if (Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
+ // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
+ // not been changed from the previous call.
+ final boolean isImmediate = (mCursorAnchorInfoMonitorMode &
+ CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE) != 0;
+ if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
Log.w(TAG, "Ignoring redundant updateCursorAnchorInfo: info=" + cursorAnchorInfo);
return;
}
@@ -1621,6 +1662,9 @@
try {
mCurMethod.updateCursorAnchorInfo(cursorAnchorInfo);
mCursorAnchorInfo = cursorAnchorInfo;
+ // Clear immediate bit (if any).
+ mCursorAnchorInfoMonitorMode &=
+ ~CursorAnchorInfoRequest.FLAG_CURSOR_ANCHOR_INFO_IMMEDIATE;
} catch (RemoteException e) {
Log.w(TAG, "IME died: " + mCurId, e);
}
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 3a1f6ed..9701c6f 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -68,6 +68,7 @@
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
@@ -5696,6 +5697,11 @@
public boolean performPrivateCommand(String action, Bundle data) {
return getTarget().performPrivateCommand(action, data);
}
+
+ @Override
+ public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) {
+ return getTarget().requestCursorAnchorInfo(request);
+ }
}
/**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 170a316..66c4b81 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -3027,8 +3027,11 @@
if (null == imm) {
return;
}
+ if (!imm.isActive(mTextView)) {
+ return;
+ }
// Skip if the IME has not requested the cursor/anchor position.
- if (!imm.isWatchingCursor(mTextView)) {
+ if (!imm.isCursorAnchorInfoEnabled()) {
return;
}
Layout layout = mTextView.getLayout();
diff --git a/core/java/android/widget/LegacyTimePickerDelegate.java b/core/java/android/widget/LegacyTimePickerDelegate.java
index 97f46ea..2216003 100644
--- a/core/java/android/widget/LegacyTimePickerDelegate.java
+++ b/core/java/android/widget/LegacyTimePickerDelegate.java
@@ -224,7 +224,8 @@
}
mDoneButton = delegator.findViewById(R.id.done_button);
- if (mDoneButton != null) {
+ mShowDoneButton = (mDoneButton != null);
+ if (mShowDoneButton) {
mDoneButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -261,7 +262,6 @@
mDelegator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
- mShowDoneButton = false;
updateDoneButton();
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 9a8380d..8ea1090 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -568,22 +568,38 @@
}
}
- mTrackDrawable.getPadding(mTempRect);
+ final int trackHeight;
+ final Rect padding = mTempRect;
+ if (mTrackDrawable != null) {
+ mTrackDrawable.getPadding(padding);
+ trackHeight = mTrackDrawable.getIntrinsicHeight();
+ } else {
+ padding.setEmpty();
+ trackHeight = 0;
+ }
+
+ final int thumbWidth;
+ final int thumbHeight;
+ if (mThumbDrawable != null) {
+ thumbWidth = mThumbDrawable.getIntrinsicWidth();
+ thumbHeight = mThumbDrawable.getIntrinsicHeight();
+ } else {
+ thumbWidth = 0;
+ thumbHeight = 0;
+ }
final int maxTextWidth = mShowText ? Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
+ mThumbTextPadding * 2 : 0;
- mThumbWidth = Math.max(maxTextWidth, mThumbDrawable.getIntrinsicWidth());
+ mThumbWidth = Math.max(maxTextWidth, thumbWidth);
final int switchWidth = Math.max(mSwitchMinWidth,
- 2 * mThumbWidth + mTempRect.left + mTempRect.right);
- final int switchHeight = Math.max(mTrackDrawable.getIntrinsicHeight(),
- mThumbDrawable.getIntrinsicHeight());
-
-
+ 2 * mThumbWidth + padding.left + padding.right);
+ final int switchHeight = Math.max(trackHeight, thumbHeight);
mSwitchWidth = switchWidth;
mSwitchHeight = switchHeight;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
final int measuredHeight = getMeasuredHeight();
if (measuredHeight < switchHeight) {
setMeasuredDimension(getMeasuredWidthAndState(), switchHeight);
@@ -830,32 +846,34 @@
@Override
public void draw(Canvas c) {
- final Rect tempRect = mTempRect;
- final Drawable trackDrawable = mTrackDrawable;
- final Drawable thumbDrawable = mThumbDrawable;
+ final Rect padding = mTempRect;
// Layout the track.
final int switchLeft = mSwitchLeft;
final int switchTop = mSwitchTop;
final int switchRight = mSwitchRight;
final int switchBottom = mSwitchBottom;
- trackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
- trackDrawable.getPadding(tempRect);
+ if (mTrackDrawable != null) {
+ mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
+ mTrackDrawable.getPadding(padding);
+ }
- final int switchInnerLeft = switchLeft + tempRect.left;
+ final int switchInnerLeft = switchLeft + padding.left;
// Relies on mTempRect, MUST be called first!
final int thumbPos = getThumbOffset();
// Layout the thumb.
- thumbDrawable.getPadding(tempRect);
- final int thumbLeft = switchInnerLeft - tempRect.left + thumbPos;
- final int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + tempRect.right;
- thumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+ if (mThumbDrawable != null) {
+ mThumbDrawable.getPadding(padding);
+ final int thumbLeft = switchInnerLeft - padding.left + thumbPos;
+ final int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + padding.right;
+ mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
- final Drawable background = getBackground();
- if (background != null) {
- background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+ final Drawable background = getBackground();
+ if (background != null) {
+ background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom);
+ }
}
// Draw the background.
@@ -866,35 +884,44 @@
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
- final Rect tempRect = mTempRect;
+ final Rect padding = mTempRect;
final Drawable trackDrawable = mTrackDrawable;
- final Drawable thumbDrawable = mThumbDrawable;
- trackDrawable.getPadding(tempRect);
+ if (trackDrawable != null) {
+ trackDrawable.getPadding(padding);
+ } else {
+ padding.setEmpty();
+ }
final int switchTop = mSwitchTop;
final int switchBottom = mSwitchBottom;
- final int switchInnerLeft = mSwitchLeft + tempRect.left;
- final int switchInnerTop = switchTop + tempRect.top;
- final int switchInnerRight = mSwitchRight - tempRect.right;
- final int switchInnerBottom = switchBottom - tempRect.bottom;
+ final int switchInnerLeft = mSwitchLeft + padding.left;
+ final int switchInnerTop = switchTop + padding.top;
+ final int switchInnerRight = mSwitchRight - padding.right;
+ final int switchInnerBottom = switchBottom - padding.bottom;
- if (mSplitTrack) {
- final Insets insets = thumbDrawable.getOpticalInsets();
- thumbDrawable.copyBounds(tempRect);
- tempRect.left += insets.left;
- tempRect.right -= insets.right;
+ final Drawable thumbDrawable = mThumbDrawable;
+ if (trackDrawable != null) {
+ if (mSplitTrack && thumbDrawable != null) {
+ final Insets insets = thumbDrawable.getOpticalInsets();
+ thumbDrawable.copyBounds(padding);
+ padding.left += insets.left;
+ padding.right -= insets.right;
- final int saveCount = canvas.save();
- canvas.clipRect(tempRect, Op.DIFFERENCE);
- trackDrawable.draw(canvas);
- canvas.restoreToCount(saveCount);
- } else {
- trackDrawable.draw(canvas);
+ final int saveCount = canvas.save();
+ canvas.clipRect(padding, Op.DIFFERENCE);
+ trackDrawable.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ } else {
+ trackDrawable.draw(canvas);
+ }
}
final int saveCount = canvas.save();
- canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
- thumbDrawable.draw(canvas);
+
+ if (thumbDrawable != null) {
+ canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
+ thumbDrawable.draw(canvas);
+ }
final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
if (switchText != null) {
@@ -904,8 +931,15 @@
}
mTextPaint.drawableState = drawableState;
- final Rect thumbBounds = thumbDrawable.getBounds();
- final int left = (thumbBounds.left + thumbBounds.right) / 2 - switchText.getWidth() / 2;
+ final int cX;
+ if (thumbDrawable != null) {
+ final Rect bounds = thumbDrawable.getBounds();
+ cX = bounds.left + bounds.right;
+ } else {
+ cX = getWidth();
+ }
+
+ final int left = cX / 2 - switchText.getWidth() / 2;
final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2;
canvas.translate(left, top);
switchText.draw(canvas);
@@ -955,11 +989,12 @@
}
private int getThumbScrollRange() {
- if (mTrackDrawable == null) {
+ if (mTrackDrawable != null) {
+ mTrackDrawable.getPadding(mTempRect);
+ return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
+ } else {
return 0;
}
- mTrackDrawable.getPadding(mTempRect);
- return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
}
@Override
@@ -1002,16 +1037,6 @@
}
@Override
- public void invalidateDrawable(Drawable drawable) {
- super.invalidateDrawable(drawable);
-
- if (drawable == mThumbDrawable) {
- // Handle changes to thumb width and height.
- requestLayout();
- }
- }
-
- @Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
}
@@ -1019,8 +1044,19 @@
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
- mThumbDrawable.jumpToCurrentState();
- mTrackDrawable.jumpToCurrentState();
+
+ if (mThumbDrawable != null) {
+ mThumbDrawable.jumpToCurrentState();
+ }
+
+ if (mTrackDrawable != null) {
+ mTrackDrawable.jumpToCurrentState();
+ }
+
+ if (mPositionAnimator != null && mPositionAnimator.isRunning()) {
+ mPositionAnimator.end();
+ mPositionAnimator = null;
+ }
}
@Override
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 8e4ba0d..5021960 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -17,6 +17,7 @@
package android.widget;
import android.annotation.Widget;
+import android.app.UiModeManager;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -29,7 +30,7 @@
import java.util.Locale;
-import static android.os.Build.VERSION_CODES.KITKAT;
+import static android.os.Build.VERSION_CODES.L;
/**
* A view for selecting the time of day, in either 24 hour or AM/PM mode. The
@@ -91,15 +92,24 @@
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TimePicker,
mDefStyleAttr, mDefStyleRes);
- // Create the correct UI delegate. Default is the legacy one.
- final boolean isLegacyMode = shouldForceLegacyMode() ?
- true : a.getBoolean(R.styleable.TimePicker_legacyMode, true);
+ // Create the correct UI delegate. Legacy mode is used when API Levels is below L
+ // release or when it is a TV UI
+ final boolean isLegacyMode = a.getBoolean(
+ R.styleable.TimePicker_legacyMode, isLegacyMode());
+
+ a.recycle();
+
setLegacyMode(isLegacyMode);
}
- private boolean shouldForceLegacyMode() {
+ private boolean isLegacyMode() {
+ UiModeManager uiModeManager =
+ (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
+ if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
+ return true;
+ }
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
- return targetSdkVersion < KITKAT;
+ return targetSdkVersion < L;
}
private TimePickerDelegate createLegacyUIDelegate(Context context, AttributeSet attrs,
@@ -113,10 +123,7 @@
defStyleRes);
}
- /**
- * @hide
- */
- public void setLegacyMode(boolean isLegacyMode) {
+ private void setLegacyMode(boolean isLegacyMode) {
removeAllViewsInLayout();
mDelegate = isLegacyMode ?
createLegacyUIDelegate(mContext, mAttrs, mDefStyleAttr, mDefStyleRes) :
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 877938e..a1a64f8 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -43,8 +43,9 @@
}
}
CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE);
+ int defaultTitleRes = 0;
if (title == null) {
- title = getResources().getText(com.android.internal.R.string.chooseActivity);
+ defaultTitleRes = com.android.internal.R.string.chooseActivity;
}
Parcelable[] pa = intent.getParcelableArrayExtra(Intent.EXTRA_INITIAL_INTENTS);
Intent[] initialIntents = null;
@@ -68,6 +69,7 @@
initialIntents[i] = in;
}
}
- super.onCreate(savedInstanceState, target, title, initialIntents, null, false);
+ super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
+ null, false);
}
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractor.aidl b/core/java/com/android/internal/app/IVoiceInteractor.aidl
index 2900595..3e0b021 100644
--- a/core/java/com/android/internal/app/IVoiceInteractor.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractor.aidl
@@ -27,6 +27,8 @@
interface IVoiceInteractor {
IVoiceInteractorRequest startConfirmation(String callingPackage,
IVoiceInteractorCallback callback, CharSequence prompt, in Bundle extras);
+ IVoiceInteractorRequest startCompleteVoice(String callingPackage,
+ IVoiceInteractorCallback callback, CharSequence message, in Bundle extras);
IVoiceInteractorRequest startAbortVoice(String callingPackage,
IVoiceInteractorCallback callback, CharSequence message, in Bundle extras);
IVoiceInteractorRequest startCommand(String callingPackage,
diff --git a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
index 8dbf9d4..dcd5759 100644
--- a/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractorCallback.aidl
@@ -26,6 +26,7 @@
oneway interface IVoiceInteractorCallback {
void deliverConfirmationResult(IVoiceInteractorRequest request, boolean confirmed,
in Bundle result);
+ void deliverCompleteVoiceResult(IVoiceInteractorRequest request, in Bundle result);
void deliverAbortVoiceResult(IVoiceInteractorRequest request, in Bundle result);
void deliverCommandResult(IVoiceInteractorRequest request, boolean complete, in Bundle result);
void deliverCancel(IVoiceInteractorRequest request);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 01b7fde..76ce765 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -18,6 +18,8 @@
import android.app.Activity;
import android.os.AsyncTask;
+import android.util.ArrayMap;
+import android.widget.AbsListView;
import android.widget.GridView;
import com.android.internal.R;
import com.android.internal.content.PackageMonitor;
@@ -89,6 +91,46 @@
}
};
+ private enum ActionTitle {
+ VIEW(Intent.ACTION_VIEW,
+ com.android.internal.R.string.whichViewApplication,
+ com.android.internal.R.string.whichViewApplicationNamed),
+ EDIT(Intent.ACTION_EDIT,
+ com.android.internal.R.string.whichEditApplication,
+ com.android.internal.R.string.whichEditApplicationNamed),
+ SEND(Intent.ACTION_SEND,
+ com.android.internal.R.string.whichSendApplication,
+ com.android.internal.R.string.whichSendApplicationNamed),
+ SENDTO(Intent.ACTION_SENDTO,
+ com.android.internal.R.string.whichSendApplication,
+ com.android.internal.R.string.whichSendApplicationNamed),
+ SEND_MULTIPLE(Intent.ACTION_SEND_MULTIPLE,
+ com.android.internal.R.string.whichSendApplication,
+ com.android.internal.R.string.whichSendApplicationNamed),
+ DEFAULT(null,
+ com.android.internal.R.string.whichApplication,
+ com.android.internal.R.string.whichApplicationNamed);
+
+ public final String action;
+ public final int titleRes;
+ public final int namedTitleRes;
+
+ ActionTitle(String action, int titleRes, int namedTitleRes) {
+ this.action = action;
+ this.titleRes = titleRes;
+ this.namedTitleRes = namedTitleRes;
+ }
+
+ public static ActionTitle forAction(String action) {
+ for (ActionTitle title : values()) {
+ if (action != null && action.equals(title.action)) {
+ return title;
+ }
+ }
+ return DEFAULT;
+ }
+ }
+
private Intent makeMyIntent() {
Intent intent = new Intent(getIntent());
intent.setComponent(null);
@@ -113,16 +155,27 @@
&& categories.contains(Intent.CATEGORY_HOME)) {
titleResource = com.android.internal.R.string.whichHomeApplication;
} else {
- titleResource = com.android.internal.R.string.whichApplication;
+ titleResource = 0;
}
- onCreate(savedInstanceState, intent, getResources().getText(titleResource),
+ onCreate(savedInstanceState, intent,
+ titleResource != 0 ? getResources().getText(titleResource) : null, titleResource,
null, null, true);
}
+ /**
+ * Compatibility version for other bundled services that use this ocerload without
+ * a default title resource
+ */
protected void onCreate(Bundle savedInstanceState, Intent intent,
- CharSequence title, Intent[] initialIntents, List<ResolveInfo> rList,
- boolean alwaysUseOption) {
+ CharSequence title, Intent[] initialIntents,
+ List<ResolveInfo> rList, boolean alwaysUseOption) {
+ onCreate(savedInstanceState, intent, title, initialIntents, rList, alwaysUseOption);
+ }
+
+ protected void onCreate(Bundle savedInstanceState, Intent intent,
+ CharSequence title, int defaultTitleRes, Intent[] initialIntents,
+ List<ResolveInfo> rList, boolean alwaysUseOption) {
setTheme(R.style.Theme_DeviceDefault_Resolver);
super.onCreate(savedInstanceState);
try {
@@ -132,7 +185,6 @@
mLaunchedFromUid = -1;
}
mPm = getPackageManager();
- mAlwaysUseOption = alwaysUseOption;
mMaxColumns = getResources().getInteger(R.integer.config_maxResolverActivityColumns);
mPackageMonitor.register(this, getMainLooper(), false);
@@ -143,14 +195,24 @@
mIconSize = am.getLauncherLargeIconSize();
mAdapter = new ResolveListAdapter(this, intent, initialIntents, rList,
- mLaunchedFromUid);
+ mLaunchedFromUid, alwaysUseOption);
+
+ final int layoutId;
+ if (mAdapter.hasFilteredItem()) {
+ layoutId = R.layout.resolver_list_with_default;
+ alwaysUseOption = false;
+ } else {
+ layoutId = R.layout.resolver_list;
+ }
+ mAlwaysUseOption = alwaysUseOption;
+
int count = mAdapter.getCount();
if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
// Gulp!
finish();
return;
} else if (count > 1) {
- setContentView(R.layout.resolver_list);
+ setContentView(layoutId);
mGridView = (GridView) findViewById(R.id.resolver_list);
mGridView.setAdapter(mAdapter);
mGridView.setOnItemClickListener(this);
@@ -162,7 +224,7 @@
resizeGrid();
} else if (count == 1) {
- startActivity(mAdapter.intentForPosition(0));
+ startActivity(mAdapter.intentForPosition(0, false));
mPackageMonitor.unregister();
mRegistered = false;
finish();
@@ -189,10 +251,19 @@
final TextView titleView = (TextView) findViewById(R.id.title);
if (titleView != null) {
+ if (title == null) {
+ title = getTitleForAction(intent.getAction(), defaultTitleRes);
+ }
titleView.setText(title);
}
- if (alwaysUseOption) {
+ final ImageView iconView = (ImageView) findViewById(R.id.icon);
+ final DisplayResolveInfo iconInfo = mAdapter.getFilteredItem();
+ if (iconView != null && iconInfo != null) {
+ new LoadIconIntoViewTask(iconView).execute(iconInfo);
+ }
+
+ if (alwaysUseOption || mAdapter.hasFilteredItem()) {
final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar);
if (buttonLayout != null) {
buttonLayout.setVisibility(View.VISIBLE);
@@ -201,12 +272,22 @@
} else {
mAlwaysUseOption = false;
}
- // Set the initial highlight if there was a preferred or last used choice
- final int initialHighlight = mAdapter.getInitialHighlight();
- if (initialHighlight >= 0) {
- mGridView.setItemChecked(initialHighlight, true);
- onItemClick(null, null, initialHighlight, 0); // Other entries are not used
- }
+ }
+
+ if (mAdapter.hasFilteredItem()) {
+ setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
+ mOnceButton.setEnabled(true);
+ }
+ }
+
+ protected CharSequence getTitleForAction(String action, int defaultTitleRes) {
+ final ActionTitle title = ActionTitle.forAction(action);
+ final boolean named = mAdapter.hasFilteredItem();
+ if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
+ return getString(defaultTitleRes);
+ } else {
+ return named ? getString(title.namedTitleRes, mAdapter.getFilteredItem().displayLabel) :
+ getString(title.titleRes);
}
}
@@ -292,7 +373,7 @@
final int checkedPos = mGridView.getCheckedItemPosition();
final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
mLastSelected = checkedPos;
- setAlwaysButtonEnabled(hasValidSelection, checkedPos);
+ setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
mOnceButton.setEnabled(hasValidSelection);
if (hasValidSelection) {
mGridView.setSelection(checkedPos);
@@ -305,21 +386,22 @@
final int checkedPos = mGridView.getCheckedItemPosition();
final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) {
- setAlwaysButtonEnabled(hasValidSelection, checkedPos);
+ setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
mOnceButton.setEnabled(hasValidSelection);
if (hasValidSelection) {
mGridView.smoothScrollToPosition(checkedPos);
}
mLastSelected = checkedPos;
} else {
- startSelected(position, false);
+ startSelected(position, false, true);
}
}
- private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos) {
+ private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
+ boolean filtered) {
boolean enabled = false;
if (hasValidSelection) {
- ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos);
+ ResolveInfo ri = mAdapter.resolveInfoForPosition(checkedPos, filtered);
if (ri.targetUserId == UserHandle.USER_CURRENT) {
enabled = true;
}
@@ -329,22 +411,25 @@
public void onButtonClick(View v) {
final int id = v.getId();
- startSelected(mGridView.getCheckedItemPosition(), id == R.id.button_always);
+ startSelected(mAlwaysUseOption ?
+ mGridView.getCheckedItemPosition() : mAdapter.getFilteredPosition(),
+ id == R.id.button_always,
+ mAlwaysUseOption);
dismiss();
}
- void startSelected(int which, boolean always) {
+ void startSelected(int which, boolean always, boolean filtered) {
if (isFinishing()) {
return;
}
- ResolveInfo ri = mAdapter.resolveInfoForPosition(which);
- Intent intent = mAdapter.intentForPosition(which);
+ ResolveInfo ri = mAdapter.resolveInfoForPosition(which, filtered);
+ Intent intent = mAdapter.intentForPosition(which, filtered);
onIntentSelected(ri, intent, always);
finish();
}
protected void onIntentSelected(ResolveInfo ri, Intent intent, boolean alwaysCheck) {
- if (mAlwaysUseOption && mAdapter.mOrigResolveList != null) {
+ if ((mAlwaysUseOption || mAdapter.hasFilteredItem()) && mAdapter.mOrigResolveList != null) {
// Build a reasonable intent filter, based on what matched.
IntentFilter filter = new IntentFilter();
@@ -485,16 +570,19 @@
List<DisplayResolveInfo> mList;
List<ResolveInfo> mOrigResolveList;
- private int mInitialHighlight = -1;
+ private int mLastChosenPosition = -1;
+ private boolean mFilterLastUsed;
public ResolveListAdapter(Context context, Intent intent,
- Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid) {
+ Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
+ boolean filterLastUsed) {
mIntent = new Intent(intent);
mInitialIntents = initialIntents;
mBaseResolveList = rList;
mLaunchedFromUid = launchedFromUid;
mInflater = LayoutInflater.from(context);
mList = new ArrayList<DisplayResolveInfo>();
+ mFilterLastUsed = filterLastUsed;
rebuildList();
}
@@ -511,8 +599,23 @@
}
}
- public int getInitialHighlight() {
- return mInitialHighlight;
+ public DisplayResolveInfo getFilteredItem() {
+ if (mFilterLastUsed && mLastChosenPosition >= 0) {
+ // Not using getItem since it offsets to dodge this position for the list
+ return mList.get(mLastChosenPosition);
+ }
+ return null;
+ }
+
+ public int getFilteredPosition() {
+ if (mFilterLastUsed && mLastChosenPosition >= 0) {
+ return mLastChosenPosition;
+ }
+ return AbsListView.INVALID_POSITION;
+ }
+
+ public boolean hasFilteredItem() {
+ return mFilterLastUsed && mLastChosenPosition >= 0;
}
private void rebuildList() {
@@ -532,7 +635,7 @@
} else {
currentResolveList = mOrigResolveList = mPm.queryIntentActivities(
mIntent, PackageManager.MATCH_DEFAULT_ONLY
- | (mAlwaysUseOption ? PackageManager.GET_RESOLVED_FILTER : 0));
+ | (mFilterLastUsed ? PackageManager.GET_RESOLVED_FILTER : 0));
// Filter out any activities that the launched uid does not
// have permission for. We don't do this when we have an explicit
// list of resolved activities, because that only happens when
@@ -648,7 +751,7 @@
&& mLastChosen.activityInfo.packageName.equals(
ro.activityInfo.packageName)
&& mLastChosen.activityInfo.name.equals(ro.activityInfo.name)) {
- mInitialHighlight = mList.size();
+ mLastChosenPosition = mList.size();
}
// No duplicate labels. Use label for entry at start
mList.add(new DisplayResolveInfo(ro, roLabel, null, null));
@@ -683,7 +786,7 @@
&& mLastChosen.activityInfo.packageName.equals(
add.activityInfo.packageName)
&& mLastChosen.activityInfo.name.equals(add.activityInfo.name)) {
- mInitialHighlight = mList.size();
+ mLastChosenPosition = mList.size();
}
if (usePkg) {
// Use application name for all entries from start to end-1
@@ -698,12 +801,12 @@
}
}
- public ResolveInfo resolveInfoForPosition(int position) {
- return mList.get(position).ri;
+ public ResolveInfo resolveInfoForPosition(int position, boolean filtered) {
+ return (filtered ? getItem(position) : mList.get(position)).ri;
}
- public Intent intentForPosition(int position) {
- DisplayResolveInfo dri = mList.get(position);
+ public Intent intentForPosition(int position, boolean filtered) {
+ DisplayResolveInfo dri = filtered ? getItem(position) : mList.get(position);
Intent intent = new Intent(dri.origIntent != null
? dri.origIntent : mIntent);
@@ -716,10 +819,17 @@
}
public int getCount() {
- return mList.size();
+ int result = mList.size();
+ if (mFilterLastUsed && mLastChosenPosition >= 0) {
+ result--;
+ }
+ return result;
}
- public Object getItem(int position) {
+ public DisplayResolveInfo getItem(int position) {
+ if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) {
+ position++;
+ }
return mList.get(position);
}
@@ -742,7 +852,7 @@
} else {
view = convertView;
}
- bindView(view, mList.get(position));
+ bindView(view, getItem(position));
return view;
}
@@ -778,7 +888,7 @@
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
- ResolveInfo ri = mAdapter.resolveInfoForPosition(position);
+ ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
showAppDetails(ri);
return true;
}
@@ -800,5 +910,27 @@
mAdapter.notifyDataSetChanged();
}
}
+
+ class LoadIconIntoViewTask extends AsyncTask<DisplayResolveInfo, Void, DisplayResolveInfo> {
+ final ImageView mTargetView;
+
+ public LoadIconIntoViewTask(ImageView target) {
+ mTargetView = target;
+ }
+
+ @Override
+ protected DisplayResolveInfo doInBackground(DisplayResolveInfo... params) {
+ final DisplayResolveInfo info = params[0];
+ if (info.displayIcon == null) {
+ info.displayIcon = loadIconForResolveInfo(info.ri);
+ }
+ return info;
+ }
+
+ @Override
+ protected void onPostExecute(DisplayResolveInfo info) {
+ mTargetView.setImageDrawable(info.displayIcon);
+ }
+ }
}
diff --git a/core/java/com/android/internal/app/ToolbarActionBar.java b/core/java/com/android/internal/app/ToolbarActionBar.java
index 7af52f3..50effd0 100644
--- a/core/java/com/android/internal/app/ToolbarActionBar.java
+++ b/core/java/com/android/internal/app/ToolbarActionBar.java
@@ -65,6 +65,7 @@
mToolbar = toolbar;
mDecorToolbar = new ToolbarWidgetWrapper(toolbar);
mWindowCallback = windowCallback;
+ mDecorToolbar.setWindowCallback(mWindowCallback);
toolbar.setOnMenuItemClickListener(mMenuClicker);
mDecorToolbar.setWindowTitle(title);
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index ac3274d..d801571 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -262,6 +262,7 @@
final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
final String systemLocale = res.getConfiguration().locale.toString();
if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
+ final String systemLanguage = res.getConfiguration().locale.getLanguage();
final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
new HashMap<String, InputMethodSubtype>();
final int N = subtypes.size();
@@ -282,15 +283,22 @@
final InputMethodSubtype subtype = subtypes.get(i);
final String locale = subtype.getLocale();
final String mode = subtype.getMode();
+ final String language = getLanguageFromLocaleString(locale);
// When system locale starts with subtype's locale, that subtype will be applicable
- // for system locale
+ // for system locale. We need to make sure the languages are the same, to prevent
+ // locales like "fil" (Filipino) being matched by "fi" (Finnish).
+ //
// For instance, it's clearly applicable for cases like system locale = en_US and
// subtype = en, but it is not necessarily considered applicable for cases like system
// locale = en and subtype = en_US.
+ //
// We just call systemLocale.startsWith(locale) in this function because there is no
// need to find applicable subtypes aggressively unlike
// findLastResortApplicableSubtypeLocked.
- if (systemLocale.startsWith(locale)) {
+ //
+ // TODO: This check is broken. It won't take scripts into account and doesn't
+ // account for the mandatory conversions performed by Locale#toString.
+ if (language.equals(systemLanguage) && systemLocale.startsWith(locale)) {
final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
// If more applicable subtypes are contained, skip.
if (applicableSubtype != null) {
@@ -335,6 +343,18 @@
}
/**
+ * Returns the language component of a given locale string.
+ */
+ public static String getLanguageFromLocaleString(String locale) {
+ final int idx = locale.indexOf('_');
+ if (idx < 0) {
+ return locale;
+ } else {
+ return locale.substring(0, idx);
+ }
+ }
+
+ /**
* If there are no selected subtypes, tries finding the most applicable one according to the
* given locale.
* @param subtypes this function will search the most applicable subtype in subtypes
@@ -353,7 +373,7 @@
if (TextUtils.isEmpty(locale)) {
locale = res.getConfiguration().locale.toString();
}
- final String language = locale.substring(0, 2);
+ final String language = getLanguageFromLocaleString(locale);
boolean partialMatchFound = false;
InputMethodSubtype applicableSubtype = null;
InputMethodSubtype firstMatchedModeSubtype = null;
@@ -361,6 +381,7 @@
for (int i = 0; i < N; ++i) {
InputMethodSubtype subtype = subtypes.get(i);
final String subtypeLocale = subtype.getLocale();
+ final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale);
// An applicable subtype should match "mode". If mode is null, mode will be ignored,
// and all subtypes with all modes can be candidates.
if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
@@ -371,7 +392,7 @@
// Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
applicableSubtype = subtype;
break;
- } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
+ } else if (!partialMatchFound && language.equals(subtypeLanguage)) {
// Partial match (e.g. system locale is "en_US" and subtype locale is "en")
applicableSubtype = subtype;
partialMatchFound = true;
diff --git a/core/java/com/android/internal/os/BatteryStatsHelper.java b/core/java/com/android/internal/os/BatteryStatsHelper.java
index 273de61..023ba03 100644
--- a/core/java/com/android/internal/os/BatteryStatsHelper.java
+++ b/core/java/com/android/internal/os/BatteryStatsHelper.java
@@ -48,6 +48,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Map;
+import java.util.Arrays;
/**
* A helper class for retrieving the power usage information for all applications and services.
@@ -82,7 +83,6 @@
private final List<BatterySipper> mMobilemsppList = new ArrayList<BatterySipper>();
private int mStatsType = BatteryStats.STATS_SINCE_CHARGED;
- private int mAsUser = 0;
long mRawRealtime;
long mRawUptime;
@@ -176,11 +176,34 @@
* Refreshes the power usage list.
*/
public void refreshStats(int statsType, int asUser) {
- refreshStats(statsType, asUser, SystemClock.elapsedRealtime() * 1000,
+ SparseArray<UserHandle> users = new SparseArray<UserHandle>(1);
+ users.put(asUser, new UserHandle(asUser));
+ refreshStats(statsType, users);
+ }
+
+ /**
+ * Refreshes the power usage list.
+ */
+ public void refreshStats(int statsType, List<UserHandle> asUsers) {
+ final int n = asUsers.size();
+ SparseArray<UserHandle> users = new SparseArray<UserHandle>(n);
+ for (int i = 0; i < n; ++i) {
+ UserHandle userHandle = asUsers.get(i);
+ users.put(userHandle.getIdentifier(), userHandle);
+ }
+ refreshStats(statsType, users);
+ }
+
+ /**
+ * Refreshes the power usage list.
+ */
+ public void refreshStats(int statsType, SparseArray<UserHandle> asUsers) {
+ refreshStats(statsType, asUsers, SystemClock.elapsedRealtime() * 1000,
SystemClock.uptimeMillis() * 1000);
}
- public void refreshStats(int statsType, int asUser, long rawRealtimeUs, long rawUptimeUs) {
+ public void refreshStats(int statsType, SparseArray<UserHandle> asUsers, long rawRealtimeUs,
+ long rawUptimeUs) {
// Initialize mStats if necessary.
getStats();
@@ -204,7 +227,6 @@
}
mStatsType = statsType;
- mAsUser = asUser;
mRawUptime = rawUptimeUs;
mRawRealtime = rawRealtimeUs;
mBatteryUptime = mStats.getBatteryUptime(rawUptimeUs);
@@ -227,7 +249,7 @@
mMaxDrainedPower = (mStats.getHighDischargeAmountSinceCharge()
* mPowerProfile.getBatteryCapacity()) / 100;
- processAppUsage();
+ processAppUsage(asUsers);
// Before aggregating apps in to users, collect all apps to sort by their ms per packet.
for (int i=0; i<mUsageList.size(); i++) {
@@ -280,7 +302,8 @@
Collections.sort(mUsageList);
}
- private void processAppUsage() {
+ private void processAppUsage(SparseArray<UserHandle> asUsers) {
+ final boolean forAllUsers = (asUsers.get(UserHandle.USER_ALL) != null);
SensorManager sensorManager = (SensorManager) mContext.getSystemService(
Context.SENSOR_SERVICE);
final int which = mStatsType;
@@ -499,7 +522,7 @@
} else if (u.getUid() == Process.BLUETOOTH_UID) {
mBluetoothSippers.add(app);
mBluetoothPower += power;
- } else if (mAsUser != UserHandle.USER_ALL && userId != mAsUser
+ } else if (!forAllUsers && asUsers.get(userId) == null
&& UserHandle.getAppId(u.getUid()) >= Process.FIRST_APPLICATION_UID) {
List<BatterySipper> list = mUserSippers.get(userId);
if (list == null) {
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index c792d78..897381d 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -25,6 +25,7 @@
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
@@ -54,6 +55,7 @@
private static final int DO_REPORT_FULLSCREEN_MODE = 100;
private static final int DO_PERFORM_PRIVATE_COMMAND = 120;
private static final int DO_CLEAR_META_KEY_STATES = 130;
+ private static final int DO_REQUEST_CURSOR_ANCHOR_INFO = 140;
private WeakReference<InputConnection> mInputConnection;
@@ -175,6 +177,11 @@
dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
}
+ public void requestCursorAnchorInfo(CursorAnchorInfoRequest request, int seq,
+ IInputContextCallback callback) {
+ dispatchMessage(obtainMessageOSC(DO_REQUEST_CURSOR_ANCHOR_INFO, request, seq, callback));
+ }
+
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
@@ -420,6 +427,23 @@
(Bundle)args.arg2);
return;
}
+ case DO_REQUEST_CURSOR_ANCHOR_INFO: {
+ SomeArgs args = (SomeArgs)msg.obj;
+ try {
+ InputConnection ic = mInputConnection.get();
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+ args.callback.setRequestCursorAnchorInfoResult(0, args.seq);
+ return;
+ }
+ args.callback.setRequestCursorAnchorInfoResult(
+ ic.requestCursorAnchorInfo((CursorAnchorInfoRequest)args.arg1),
+ args.seq);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e);
+ }
+ return;
+ }
}
Log.w(TAG, "Unhandled message code: " + msg.what);
}
@@ -449,7 +473,15 @@
args.seq = seq;
return mH.obtainMessage(what, arg1, arg2, args);
}
-
+
+ Message obtainMessageOSC(int what, Object arg1, int seq, IInputContextCallback callback) {
+ SomeArgs args = new SomeArgs();
+ args.arg1 = arg1;
+ args.callback = callback;
+ args.seq = seq;
+ return mH.obtainMessage(what, 0, 0, args);
+ }
+
Message obtainMessageIOSC(int what, int arg1, Object arg2, int seq,
IInputContextCallback callback) {
SomeArgs args = new SomeArgs();
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index 719a24f..c06596a 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -20,6 +20,7 @@
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
import android.view.inputmethod.ExtractedTextRequest;
import com.android.internal.view.IInputContextCallback;
@@ -73,4 +74,6 @@
void getSelectedText(int flags, int seq, IInputContextCallback callback);
+ void requestCursorAnchorInfo(in CursorAnchorInfoRequest request, int seq,
+ IInputContextCallback callback);
}
diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl
index 661066b..ab2fbdc 100644
--- a/core/java/com/android/internal/view/IInputContextCallback.aidl
+++ b/core/java/com/android/internal/view/IInputContextCallback.aidl
@@ -27,4 +27,5 @@
void setCursorCapsMode(int capsMode, int seq);
void setExtractedText(in ExtractedText extractedText, int seq);
void setSelectedText(CharSequence selectedText, int seq);
+ void setRequestCursorAnchorInfoResult(int result, int seq);
}
diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl
index b100d27..89d36ff 100644
--- a/core/java/com/android/internal/view/IInputMethodClient.aidl
+++ b/core/java/com/android/internal/view/IInputMethodClient.aidl
@@ -27,6 +27,5 @@
void onBindMethod(in InputBindResult res);
void onUnbindMethod(int sequence);
void setActive(boolean active);
- void setCursorAnchorMonitorMode(int monitorMode);
void setUserActionNotificationSequenceNumber(int sequenceNumber);
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index b84c359..6f104dd 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -78,5 +78,4 @@
void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
int getInputMethodWindowVisibleHeight();
oneway void notifyUserAction(int sequenceNumber);
- void setCursorAnchorMonitorMode(in IBinder token, int monitorMode);
}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 9024d8d..8535a98 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -23,6 +23,7 @@
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
@@ -40,6 +41,7 @@
public CharSequence mSelectedText;
public ExtractedText mExtractedText;
public int mCursorCapsMode;
+ public int mCursorAnchorInfoRequestResult;
// A 'pool' of one InputContextCallback. Each ICW request will attempt to gain
// exclusive access to this object.
@@ -152,7 +154,20 @@
}
}
}
-
+
+ public void setRequestCursorAnchorInfoResult(int result, int seq) {
+ synchronized (this) {
+ if (seq == mSeq) {
+ mCursorAnchorInfoRequestResult = result;
+ mHaveValue = true;
+ notifyAll();
+ } else {
+ Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
+ + ") in setCursorAnchorInfoRequestResult, ignoring.");
+ }
+ }
+ }
+
/**
* Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
*
@@ -413,4 +428,22 @@
return false;
}
}
+
+ public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) {
+ int value = CursorAnchorInfoRequest.RESULT_NOT_HANDLED;
+ try {
+ InputContextCallback callback = InputContextCallback.getInstance();
+ mIInputContext.requestCursorAnchorInfo(request, callback.mSeq, callback);
+ synchronized (callback) {
+ callback.waitForResultLocked();
+ if (callback.mHaveValue) {
+ value = callback.mCursorAnchorInfoRequestResult;
+ }
+ }
+ callback.dispose();
+ } catch (RemoteException e) {
+ return CursorAnchorInfoRequest.RESULT_NOT_HANDLED;
+ }
+ return value;
+ }
}
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 4cbdf78..10a7794 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -25,6 +25,7 @@
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.CursorAnchorInfoRequest;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.widget.TextView;
@@ -185,4 +186,18 @@
return success;
}
+
+ @Override
+ public int requestCursorAnchorInfo(CursorAnchorInfoRequest request) {
+ if (DEBUG) Log.v(TAG, "requestCursorAnchorInfo " + request);
+ final int result = super.requestCursorAnchorInfo(request);
+ if (mIMM != null && request != null && (request.getRequestType() ==
+ CursorAnchorInfoRequest.TYPE_CURSOR_ANCHOR_INFO)) {
+ mIMM.setCursorAnchorInfoMonitorMode(request.getRequestFlags());
+ // One-shot event is not yet fully supported.
+ // TODO: Support one-shot event correctly.
+ return CursorAnchorInfoRequest.RESULT_SCHEDULED;
+ }
+ return result;
+ }
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ef7ef0a..0e22174 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -147,6 +147,7 @@
android_hardware_UsbDevice.cpp \
android_hardware_UsbDeviceConnection.cpp \
android_hardware_UsbRequest.cpp \
+ android_hardware_location_ActivityRecognitionHardware.cpp \
android_util_FileObserver.cpp \
android/opengl/poly_clip.cpp.arm \
android/opengl/util.cpp.arm \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 9b66734..92a8fca 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -87,6 +87,7 @@
extern int register_android_hardware_UsbDevice(JNIEnv *env);
extern int register_android_hardware_UsbDeviceConnection(JNIEnv *env);
extern int register_android_hardware_UsbRequest(JNIEnv *env);
+extern int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env);
extern int register_android_media_AudioRecord(JNIEnv *env);
extern int register_android_media_AudioSystem(JNIEnv *env);
@@ -1323,6 +1324,7 @@
REG_JNI(register_android_hardware_UsbDevice),
REG_JNI(register_android_hardware_UsbDeviceConnection),
REG_JNI(register_android_hardware_UsbRequest),
+ REG_JNI(register_android_hardware_location_ActivityRecognitionHardware),
REG_JNI(register_android_media_AudioRecord),
REG_JNI(register_android_media_AudioSystem),
REG_JNI(register_android_media_AudioTrack),
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 0cfcaef..c71f2261 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -56,19 +56,20 @@
SkSafeUnref(shader);
}
-static void Shader_setLocalMatrix(JNIEnv* env, jobject o, jlong shaderHandle,
+static jlong Shader_setLocalMatrix(JNIEnv* env, jobject o, jlong shaderHandle,
jlong matrixHandle)
{
SkShader* shader = reinterpret_cast<SkShader*>(shaderHandle);
const SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixHandle);
if (shader) {
if (NULL == matrix) {
- shader->resetLocalMatrix();
+ matrix = &SkMatrix::I();
}
- else {
- shader->setLocalMatrix(*matrix);
- }
+ SkShader* newShader = SkShader::CreateLocalMatrixShader(shader, *matrix);
+ shader->unref();
+ shader = newShader;
}
+ return reinterpret_cast<jlong>(shader);
}
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -239,7 +240,7 @@
static JNINativeMethod gShaderMethods[] = {
{ "nativeDestructor", "(J)V", (void*)Shader_destructor },
- { "nativeSetLocalMatrix", "(JJ)V", (void*)Shader_setLocalMatrix }
+ { "nativeSetLocalMatrix", "(JJ)J", (void*)Shader_setLocalMatrix }
};
static JNINativeMethod gBitmapShaderMethods[] = {
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
new file mode 100644
index 0000000..5b542ba
--- /dev/null
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ActivityRecognitionHardware"
+
+#include <jni.h>
+#include <JNIHelp.h>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+
+#include "activity_recognition.h"
+
+
+// keep base connection data from the HAL
+static activity_recognition_module_t* sModule = NULL;
+static activity_recognition_device_t* sDevice = NULL;
+
+static jobject sCallbacksObject = NULL;
+static jmethodID sOnActivityChanged = NULL;
+
+
+static void check_and_clear_exceptions(JNIEnv* env, const char* method_name) {
+ if (!env->ExceptionCheck()) {
+ return;
+ }
+
+ ALOGE("An exception was thrown by '%s'.", method_name);
+ LOGE_EX(env);
+ env->ExceptionClear();
+}
+
+static jint attach_thread(JNIEnv** env) {
+ JavaVM* java_vm = android::AndroidRuntime::getJavaVM();
+ assert(java_vm != NULL);
+
+ JavaVMAttachArgs args = {
+ JNI_VERSION_1_6,
+ "ActivityRecognition HAL callback.",
+ NULL /* group */
+ };
+
+ jint result = java_vm->AttachCurrentThread(env, &args);
+ if (result != JNI_OK) {
+ ALOGE("Attach to callback thread failed: %d", result);
+ }
+
+ return result;
+}
+
+static jint detach_thread() {
+ JavaVM* java_vm = android::AndroidRuntime::getJavaVM();
+ assert(java_vm != NULL);
+
+ jint result = java_vm->DetachCurrentThread();
+ if (result != JNI_OK) {
+ ALOGE("Detach of callback thread failed: %d", result);
+ }
+
+ return result;
+}
+
+
+/**
+ * Handle activity recognition events from HAL.
+ */
+static void activity_callback(
+ const activity_recognition_callback_procs_t* procs,
+ const activity_event_t* events,
+ int count) {
+ if (sOnActivityChanged == NULL) {
+ ALOGE("Dropping activity_callback because onActivityChanged handler is null.");
+ return;
+ }
+
+ if (events == NULL || count <= 0) {
+ ALOGE("Invalid activity_callback. Count: %d, Events: %p", count, events);
+ return;
+ }
+
+ JNIEnv* env = NULL;
+ int result = attach_thread(&env);
+ if (result != JNI_OK) {
+ return;
+ }
+
+ jclass event_class =
+ env->FindClass("android/hardware/location/ActivityRecognitionHardware$Event");
+ jmethodID event_ctor = env->GetMethodID(event_class, "<init>", "()V");
+ jfieldID activity_field = env->GetFieldID(event_class, "activity", "I");
+ jfieldID type_field = env->GetFieldID(event_class, "type", "I");
+ jfieldID timestamp_field = env->GetFieldID(event_class, "timestamp", "J");
+
+ jobjectArray events_array = env->NewObjectArray(count, event_class, NULL);
+ for (int i = 0; i < count; ++i) {
+ const activity_event_t* event = &events[i];
+ jobject event_object = env->NewObject(event_class, event_ctor);
+ env->SetIntField(event_object, activity_field, event->activity);
+ env->SetIntField(event_object, type_field, event->event_type);
+ env->SetLongField(event_object, timestamp_field, event->timestamp);
+ env->SetObjectArrayElement(events_array, i, event_object);
+ env->DeleteLocalRef(event_object);
+ }
+
+ env->CallVoidMethod(sCallbacksObject, sOnActivityChanged, events_array);
+ check_and_clear_exceptions(env, __FUNCTION__);
+
+ // TODO: ideally we'd let the HAL register the callback thread only once
+ detach_thread();
+}
+
+activity_recognition_callback_procs_t sCallbacks {
+ activity_callback,
+};
+
+/**
+ * Initializes the ActivityRecognitionHardware class from the native side.
+ */
+static void class_init(JNIEnv* env, jclass clazz) {
+ // open the hardware module
+ int error = hw_get_module(
+ ACTIVITY_RECOGNITION_HARDWARE_MODULE_ID,
+ (const hw_module_t**) &sModule);
+ if (error != 0) {
+ ALOGE("Error hw_get_module: %d", error);
+ return;
+ }
+
+ error = activity_recognition_open(&sModule->common, &sDevice);
+ if (error != 0) {
+ ALOGE("Error opening device: %d", error);
+ return;
+ }
+
+ // get references to the Java provided methods
+ sOnActivityChanged = env->GetMethodID(
+ clazz,
+ "onActivityChanged",
+ "([Landroid/hardware/location/ActivityRecognitionHardware$Event;)V");
+ if (sOnActivityChanged == NULL) {
+ ALOGE("Error obtaining ActivityChanged callback.");
+ return;
+ }
+
+ // register callbacks
+ sDevice->register_activity_callback(sDevice, &sCallbacks);
+}
+
+/**
+ * Initializes and connect the callbacks handlers in the HAL.
+ */
+static void initialize(JNIEnv* env, jobject obj) {
+ if (sCallbacksObject == NULL) {
+ sCallbacksObject = env->NewGlobalRef(obj);
+ } else {
+ ALOGD("Callbacks Object was already initialized.");
+ }
+
+ if (sDevice != NULL) {
+ sDevice->register_activity_callback(sDevice, &sCallbacks);
+ } else {
+ ALOGD("ActivityRecognition device not found during initialization.");
+ }
+}
+
+/**
+ * De-initializes the ActivityRecognitionHardware from the native side.
+ */
+static void release(JNIEnv* env, jobject obj) {
+ if (sDevice == NULL) {
+ return;
+ }
+
+ int error = activity_recognition_close(sDevice);
+ if (error != 0) {
+ ALOGE("Error closing device: %d", error);
+ return;
+ }
+}
+
+/**
+ * Returns true if ActivityRecognition HAL is supported, false otherwise.
+ */
+static jboolean is_supported(JNIEnv* env, jclass clazz) {
+ if (sModule != NULL && sDevice != NULL ) {
+ return JNI_TRUE;
+ }
+ return JNI_FALSE;
+}
+
+/**
+ * Gets an array representing the supported activities.
+ */
+static jobjectArray get_supported_activities(JNIEnv* env, jobject obj) {
+ if (sModule == NULL) {
+ return NULL;
+ }
+
+ char const* const* list = NULL;
+ int list_size = sModule->get_supported_activities_list(sModule, &list);
+ if (list_size <= 0 || list == NULL) {
+ return NULL;
+ }
+
+ jclass string_class = env->FindClass("java/lang/String;");
+ if (string_class == NULL) {
+ ALOGE("Unable to find String class for supported activities.");
+ return NULL;
+ }
+
+ jobjectArray string_array = env->NewObjectArray(list_size, string_class, NULL);
+ if (string_array == NULL) {
+ ALOGE("Unable to create string array for supported activities.");
+ return NULL;
+ }
+
+ for (int i = 0; i < list_size; ++i) {
+ const char* string_ptr = const_cast<const char*>(list[i]);
+ jsize string_length = strlen(string_ptr);
+ jstring string = env->NewString((const jchar*) string_ptr, string_length);
+ env->SetObjectArrayElement(string_array, i, string);
+
+ // log debugging information in case we need to try to trace issues with the strings
+ if (string_length) {
+ ALOGD("Invalid activity (index=%d) name size: %d", i, string_length);
+ }
+ }
+
+ return string_array;
+}
+
+/**
+ * Enables a given activity event to be actively monitored.
+ */
+static int enable_activity_event(
+ JNIEnv* env,
+ jobject obj,
+ jint activity_handle,
+ jint event_type,
+ jlong report_latency_ns) {
+ return sDevice->enable_activity_event(
+ sDevice,
+ (uint32_t) activity_handle,
+ (uint32_t) event_type,
+ report_latency_ns);
+}
+
+/**
+ * Disables a given activity event from being actively monitored.
+ */
+static int disable_activity_event(
+ JNIEnv* env,
+ jobject obj,
+ jint activity_handle,
+ jint event_type) {
+ return sDevice->disable_activity_event(
+ sDevice,
+ (uint32_t) activity_handle,
+ (uint32_t) event_type);
+}
+
+/**
+ * Request flush for al batch buffers.
+ */
+static int flush(JNIEnv* env, jobject obj) {
+ return sDevice->flush(sDevice);
+}
+
+
+static JNINativeMethod sMethods[] = {
+ // {"name", "signature", (void*) functionPointer },
+ { "nativeClassInit", "()V", (void*) class_init },
+ { "nativeInitialize", "()V", (void*) initialize },
+ { "nativeRelease", "()V", (void*) release },
+ { "nativeIsSupported", "()Z", (void*) is_supported },
+ { "nativeGetSupportedActivities", "()[Ljava/lang/String;", (void*) get_supported_activities },
+ { "nativeEnableActivityEvent", "(IIJ)I", (void*) enable_activity_event },
+ { "nativeDisableActivityEvent", "(II)I", (void*) disable_activity_event },
+ { "nativeFlush", "()I", (void*) flush },
+};
+
+/**
+ * Registration method invoked in JNI load.
+ */
+int register_android_hardware_location_ActivityRecognitionHardware(JNIEnv* env) {
+ return jniRegisterNativeMethods(
+ env,
+ "android/hardware/location/ActivityRecognitionHardware",
+ sMethods,
+ NELEM(sMethods));
+}
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index a75d547..760ed45 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -246,23 +246,11 @@
return env->NewStringUTF(::dhcp_get_errmsg());
}
-static void android_net_utils_markSocket(JNIEnv *env, jobject thiz, jint socket, jint mark)
-{
- if (setsockopt(socket, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) {
- jniThrowException(env, "java/lang/IllegalStateException", "Error marking socket");
- }
-}
-
static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
{
return (jboolean) !setNetworkForProcess(netId);
}
-static jboolean android_net_utils_unbindProcessToNetwork(JNIEnv *env, jobject thiz)
-{
- return (jboolean) !setNetworkForProcess(NETID_UNSET);
-}
-
static jint android_net_utils_getNetworkBoundToProcess(JNIEnv *env, jobject thiz)
{
return getNetworkForProcess();
@@ -274,11 +262,6 @@
return (jboolean) !setNetworkForResolv(netId);
}
-static jboolean android_net_utils_unbindProcessToNetworkForHostResolution(JNIEnv *env, jobject thiz)
-{
- return (jboolean) !setNetworkForResolv(NETID_UNSET);
-}
-
static jboolean android_net_utils_bindSocketToNetwork(JNIEnv *env, jobject thiz, jint socket,
jint netId)
{
@@ -306,12 +289,9 @@
{ "stopDhcp", "(Ljava/lang/String;)Z", (void *)android_net_utils_stopDhcp },
{ "releaseDhcpLease", "(Ljava/lang/String;)Z", (void *)android_net_utils_releaseDhcpLease },
{ "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
- { "markSocket", "(II)V", (void*) android_net_utils_markSocket },
{ "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
{ "getNetworkBoundToProcess", "()I", (void*) android_net_utils_getNetworkBoundToProcess },
- { "unbindProcessToNetwork", "()Z", (void*) android_net_utils_unbindProcessToNetwork },
{ "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
- { "unbindProcessToNetworkForHostResolution", "()Z", (void*) android_net_utils_unbindProcessToNetworkForHostResolution },
{ "bindSocketToNetwork", "(II)Z", (void*) android_net_utils_bindSocketToNetwork },
{ "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
};
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c34a971..4725cfb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2602,16 +2602,6 @@
android:description="@string/permdesc_modifyNetworkAccounting"
android:protectionLevel="signature|system" />
- <!-- @SystemApi Allows an application to mark traffic as from another user for per user routing.
- Used by system wide services like media server that execute delegated network connections
- for users.
- @hide
- -->
- <permission android:name="android.permission.MARK_NETWORK_SOCKET"
- android:label="@string/permlab_markNetworkSocket"
- android:description="@string/permdesc_markNetworkSocket"
- android:protectionLevel="signature|system" />
-
<!-- C2DM permission.
@hide Used internally.
-->
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 773b386..8e57543 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -20,7 +20,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:maxWidth="400dp"
+ android:maxWidth="@dimen/resolver_max_width"
android:maxCollapsedHeight="260dp"
android:maxCollapsedHeightSmall="56dp"
android:id="@id/contentPanel"
@@ -30,7 +30,7 @@
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:layout_alwaysShow="true"
- android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textAppearance="?android:attr/textAppearanceMedium"
android:gravity="start|center_vertical"
android:paddingLeft="32dp"
android:paddingRight="32dp"
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
new file mode 100644
index 0000000..0bd0e14
--- /dev/null
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 2014, The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+<com.android.internal.widget.ResolverDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:maxWidth="@dimen/resolver_max_width"
+ android:maxCollapsedHeight="48dp"
+ android:id="@id/contentPanel"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alwaysShow="true"
+ android:orientation="vertical"
+ android:background="@color/white"
+ android:elevation="8dp" >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="80dp"
+ android:paddingStart="32dp"
+ android:paddingEnd="32dp"
+ android:orientation="horizontal"
+ >
+
+ <TextView android:id="@+id/title"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="?android:attr/listPreferredItemHeight"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:gravity="start|center_vertical"
+ android:paddingEnd="16dp"
+ />
+ <ImageView android:id="@+id/icon"
+ android:layout_width="56dp"
+ android:layout_height="56dp"
+ android:layout_gravity="center_vertical"
+ android:scaleType="fitCenter"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/button_bar"
+ android:visibility="gone"
+ style="?android:attr/buttonBarStyle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alwaysShow="true"
+ android:gravity="end"
+ android:orientation="horizontal"
+ android:layoutDirection="locale"
+ android:measureWithLargestChild="true"
+ android:paddingBottom="16dp"
+ android:paddingStart="32dp"
+ android:paddingEnd="32dp"
+ android:background="@color/white"
+ android:elevation="8dp">
+ <Button android:id="@+id/button_once"
+ android:layout_width="wrap_content"
+ android:layout_gravity="start"
+ android:maxLines="2"
+ style="?android:attr/buttonBarNegativeButtonStyle"
+ android:minHeight="@dimen/alert_dialog_button_bar_height"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="@string/activity_resolver_use_once"
+ android:onClick="onButtonClick" />
+ <Button android:id="@+id/button_always"
+ android:layout_width="wrap_content"
+ android:layout_gravity="end"
+ android:maxLines="2"
+ android:minHeight="@dimen/alert_dialog_button_bar_height"
+ style="?android:attr/buttonBarPositiveButtonStyle"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="@string/activity_resolver_use_always"
+ android:onClick="onButtonClick" />
+ </LinearLayout>
+ <View android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?android:attr/dividerVertical" />
+ </LinearLayout>
+
+ <GridView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/resolver_list"
+ android:numColumns="4"
+ android:columnWidth="128dp"
+ android:clipToPadding="false"
+ android:scrollbarStyle="outsideOverlay"
+ android:paddingLeft="32dp"
+ android:paddingRight="32dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:background="@color/white"
+ android:elevation="8dp"
+ android:nestedScrollingEnabled="true"
+ />
+
+</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/layout/screen_action_bar.xml b/core/res/res/layout/screen_action_bar.xml
index 5acb588..b3a3478 100644
--- a/core/res/res/layout/screen_action_bar.xml
+++ b/core/res/res/layout/screen_action_bar.xml
@@ -35,6 +35,7 @@
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:transitionName="android:action_bar"
+ android:touchscreenBlocksFocus="true"
android:gravity="top">
<com.android.internal.widget.ActionBarView
android:id="@+id/action_bar"
@@ -53,5 +54,6 @@
android:layout_height="wrap_content"
style="?attr/actionBarSplitStyle"
android:visibility="gone"
+ android:touchscreenBlocksFocus="true"
android:gravity="center"/>
</com.android.internal.widget.ActionBarOverlayLayout>
diff --git a/core/res/res/layout/screen_toolbar.xml b/core/res/res/layout/screen_toolbar.xml
index 56815f8..039e89f 100644
--- a/core/res/res/layout/screen_toolbar.xml
+++ b/core/res/res/layout/screen_toolbar.xml
@@ -35,6 +35,7 @@
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:transitionName="android:action_bar"
+ android:touchscreenBlocksFocus="true"
android:gravity="top">
<Toolbar
android:id="@+id/action_bar"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9438fcc..5fdadb7 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -985,6 +985,14 @@
<!-- The color applied to framework buttons in their normal state. -->
<attr name="colorButtonNormal" format="color" />
+
+ <!-- ================== -->
+ <!-- Hardware rendering -->
+ <!-- ================== -->
+ <eat-comment />
+
+ <!-- Reference to the Lighting style. -->
+ <attr name="lightingStyle" format="reference" />
</declare-styleable>
<!-- **************************************************************** -->
@@ -7141,4 +7149,13 @@
<attr name="layout_ignoreOffset" format="boolean" />
<attr name="layout_gravity" />
</declare-styleable>
+
+ <!-- @hide -->
+ <declare-styleable name="Lighting">
+ <attr name="lightY" format="dimension" />
+ <attr name="lightZ" format="dimension" />
+ <attr name="lightRadius" format="dimension" />
+ <attr name="ambientShadowAlpha" format="float" />
+ <attr name="spotShadowAlpha" format="float" />
+ </declare-styleable>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4585b78..00f49a1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -835,6 +835,19 @@
config_enableGeofenceOverlay is false. -->
<string name="config_geofenceProviderPackageName" translatable="false">@null</string>
+ <!-- Whether to enable Hardware Activity-Recognition overlay which allows Hardware
+ Activity-Recognition to be replaced by an app at run-time. When disabled, only the
+ config_activityRecognitionHardwarePackageName package will be searched for
+ its implementation, otherwise packages whose signature matches the
+ signatures of config_locationProviderPackageNames will be searched, and
+ the service with the highest version number will be picked. Anyone who
+ wants to disable the overlay mechanism can set it to false.
+ -->
+ <bool name="config_enableActivityRecognitionHardwareOverlay" translatable="false">true</bool>
+ <!-- Package name providing Hardware Activity-Recognition API support. Used only when
+ config_enableActivityRecognitionHardwareOverlay is false. -->
+ <string name="config_activityRecognitionHardwarePackageName" translatable="false">@null</string>
+
<!-- Package name(s) containing location provider support.
These packages can contain services implementing location providers,
such as the Geocode Provider, Network Location Provider, and
@@ -1621,4 +1634,6 @@
the IMS connection -->
<bool name="useImsAlwaysForEmergencyCall">true</bool>
+ <bool name="config_networkSamplingWakesDevice">true</bool>
+
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d2e023d..ad6c6cd7 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -365,4 +365,5 @@
<!-- width of ImmersiveModeConfirmation (-1 for match_parent) -->
<dimen name="immersive_mode_cling_width">-1px</dimen>
+ <dimen name="resolver_max_width">480dp</dimen>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7c60c6e..5d57262 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2109,11 +2109,6 @@
<string name="permdesc_modifyNetworkAccounting">Allows the app to modify how network usage is accounted against apps. Not for use by normal apps.</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_markNetworkSocket">modify socket marks</string>
- <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_markNetworkSocket">Allows the app to modify socket marks for routing</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_accessNotifications">access notifications</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_accessNotifications">Allows the app to retrieve, examine, and clear notifications, including those posted by other apps.</string>
@@ -3385,6 +3380,24 @@
<!-- Title of intent resolver dialog when selecting an application to run. -->
<string name="whichApplication">Complete action using</string>
+ <!-- Title of intent resolver dialog when selecting an application to run
+ and a previously used application is known. -->
+ <string name="whichApplicationNamed">Complete action using %1$s</string>
+ <!-- Title of intent resolver dialog when selecting a viewer application to run. -->
+ <string name="whichViewApplication">Open with</string>
+ <!-- Title of intent resolver dialog when selecting a viewer application to run
+ and a previously used application is known. -->
+ <string name="whichViewApplicationNamed">Open with %1$s</string>
+ <!-- Title of intent resolver dialog when selecting an editor application to run. -->
+ <string name="whichEditApplication">Edit with</string>
+ <!-- Title of intent resolver dialog when selecting an editor application to run
+ and a previously used application is known. -->
+ <string name="whichEditApplicationNamed">Edit with %1$s</string>
+ <!-- Title of intent resolver dialog when selecting a sharing application to run. -->
+ <string name="whichSendApplication">Share with</string>
+ <!-- Title of intent resolver dialog when selecting a sharing application to run
+ and a previously used application is known. -->
+ <string name="whichSendApplicationNamed">Share with %1$s</string>
<!-- Title of intent resolver dialog when selecting a HOME application to run. -->
<string name="whichHomeApplication">Select a home app</string>
<!-- Option to always use the selected application resolution in the future. See the "Complete action using" dialog title-->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 7f216e5..0eceae6 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1206,6 +1206,7 @@
<item name="navigationButtonStyle">@style/Widget.Toolbar.Button.Navigation</item>
<item name="collapseIcon">?attr/homeAsUpIndicator</item>
<item name="contentInsetStart">16dp</item>
+ <item name="touchscreenBlocksFocus">true</item>
</style>
<style name="Widget.Toolbar.Button.Navigation" parent="Widget">
@@ -1361,4 +1362,12 @@
<item name="padding">16dp</item>
</style>
+ <style name="Lighting">
+ <item name="lightY">-200dp</item>
+ <item name="lightZ">800dp</item>
+ <item name="lightRadius">800dp</item>
+ <item name="ambientShadowAlpha">0.125</item>
+ <item name="spotShadowAlpha">0.25</item>
+ </style>
+
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cf0f7b7..cd7394a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -253,6 +253,7 @@
<java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" />
<java-symbol type="bool" name="config_enable_puk_unlock_screen" />
<java-symbol type="bool" name="config_mms_content_disposition_support" />
+ <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
<java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
<java-symbol type="bool" name="config_sip_wifi_only" />
<java-symbol type="bool" name="config_sms_capable" />
@@ -1477,6 +1478,7 @@
<java-symbol type="bool" name="config_useAttentionLight" />
<java-symbol type="bool" name="config_animateScreenLights" />
<java-symbol type="bool" name="config_automatic_brightness_available" />
+ <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
<java-symbol type="bool" name="config_enableFusedLocationOverlay" />
<java-symbol type="bool" name="config_enableHardwareFlpOverlay" />
<java-symbol type="bool" name="config_enableGeocoderOverlay" />
@@ -1569,6 +1571,7 @@
<java-symbol type="string" name="car_mode_disable_notification_title" />
<java-symbol type="string" name="chooser_wallpaper" />
<java-symbol type="string" name="config_datause_iface" />
+ <java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
<java-symbol type="string" name="config_fusedLocationProviderPackageName" />
<java-symbol type="string" name="config_hardwareFlpPackageName" />
<java-symbol type="string" name="config_geocoderProviderPackageName" />
@@ -1901,8 +1904,21 @@
<java-symbol type="attr" name="seekBarPreferenceStyle" />
<java-symbol type="style" name="Theme.DeviceDefault.Resolver" />
<java-symbol type="attr" name="preferenceFragmentStyle" />
-
<java-symbol type="bool" name="skipHoldBeforeMerge" />
<java-symbol type="bool" name="useImsAlwaysForEmergencyCall" />
<java-symbol type="attr" name="touchscreenBlocksFocus" />
+ <java-symbol type="layout" name="resolver_list_with_default" />
+ <java-symbol type="string" name="whichApplicationNamed" />
+ <java-symbol type="string" name="whichViewApplication" />
+ <java-symbol type="string" name="whichViewApplicationNamed" />
+ <java-symbol type="string" name="whichEditApplication" />
+ <java-symbol type="string" name="whichEditApplicationNamed" />
+ <java-symbol type="string" name="whichSendApplication" />
+ <java-symbol type="string" name="whichSendApplicationNamed" />
+ <java-symbol type="attr" name="lightingStyle" />
+ <java-symbol type="attr" name="lightY" />
+ <java-symbol type="attr" name="lightZ" />
+ <java-symbol type="attr" name="lightRadius" />
+ <java-symbol type="attr" name="ambientShadowAlpha" />
+ <java-symbol type="attr" name="spotShadowAlpha" />
</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index d61253f..6ada975 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -420,6 +420,9 @@
<!-- Accessibility focused drawable. -->
<item name="accessibilityFocusedDrawable">@drawable/view_accessibility_focused</item>
+
+ <!-- Lighting and shadow style. -->
+ <item name="lightingStyle">@style/Lighting</item>
</style>
<!-- Variant of {@link #Theme} with no title bar -->
diff --git a/core/res/res/values/themes_holo.xml b/core/res/res/values/themes_holo.xml
index a9150c6..76bfc4b 100644
--- a/core/res/res/values/themes_holo.xml
+++ b/core/res/res/values/themes_holo.xml
@@ -942,11 +942,7 @@
<!-- Holo theme for the TimePicker dialog windows, which is used by the
{@link android.app.TimePickerDialog} class. -->
- <style name="Theme.Holo.Dialog.TimePicker">
- <item name="windowBackground">@color/transparent</item>
- <item name="windowTitleStyle">@style/DialogWindowTitle.Holo</item>
- <item name="windowContentOverlay">@null</item>
- </style>
+ <style name="Theme.Holo.Dialog.TimePicker" parent="Theme.Holo.Dialog.Alert" />
<!-- Theme for a window that will be displayed either full-screen on
smaller screens (small, normal) or as a dialog on larger screens
@@ -1061,11 +1057,7 @@
<!-- Holo Light theme for the TimePicker dialog windows, which is used by the
{@link android.app.TimePickerDialog} class. -->
- <style name="Theme.Holo.Light.Dialog.TimePicker">
- <item name="windowBackground">@color/transparent</item>
- <item name="windowTitleStyle">@style/DialogWindowTitle.Holo.Light</item>
- <item name="windowContentOverlay">@null</item>
- </style>
+ <style name="Theme.Holo.Light.Dialog.TimePicker" parent="Theme.Holo.Light.Dialog.Alert" />
<!-- Theme for a presentation window on a secondary display. -->
<style name="Theme.Holo.Light.Dialog.Presentation" parent="Theme.Holo.Light.NoActionBar.Fullscreen" />
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java
index bf34f1d..e0a3a03 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanFilterTest.java
@@ -56,20 +56,20 @@
@SmallTest
public void testsetNameFilter() {
- ScanFilter filter = mFilterBuilder.setName("Ped").build();
+ ScanFilter filter = mFilterBuilder.setDeviceName("Ped").build();
assertTrue("setName filter fails", filter.matches(mScanResult));
- filter = mFilterBuilder.setName("Pem").build();
+ filter = mFilterBuilder.setDeviceName("Pem").build();
assertFalse("setName filter fails", filter.matches(mScanResult));
}
@SmallTest
public void testDeviceFilter() {
- ScanFilter filter = mFilterBuilder.setMacAddress(DEVICE_MAC).build();
+ ScanFilter filter = mFilterBuilder.setDeviceAddress(DEVICE_MAC).build();
assertTrue("device filter fails", filter.matches(mScanResult));
- filter = mFilterBuilder.setMacAddress("11:22:33:44:55:66").build();
+ filter = mFilterBuilder.setDeviceAddress("11:22:33:44:55:66").build();
assertFalse("device filter fails", filter.matches(mScanResult));
}
@@ -134,10 +134,10 @@
ScanFilter filter = mFilterBuilder.build();
testReadWriteParcelForFilter(filter);
- filter = mFilterBuilder.setName("Ped").build();
+ filter = mFilterBuilder.setDeviceName("Ped").build();
testReadWriteParcelForFilter(filter);
- filter = mFilterBuilder.setMacAddress("11:22:33:44:55:66").build();
+ filter = mFilterBuilder.setDeviceAddress("11:22:33:44:55:66").build();
testReadWriteParcelForFilter(filter);
filter = mFilterBuilder.setServiceUuid(
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
index cece96b..e259bcc 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/le/ScanRecordTest.java
@@ -51,7 +51,7 @@
assertTrue(data.getServiceUuids().contains(uuid1));
assertTrue(data.getServiceUuids().contains(uuid2));
- assertEquals("Ped", data.getLocalName());
+ assertEquals("Ped", data.getDeviceName());
assertEquals(-20, data.getTxPowerLevel());
assertEquals(224, data.getManufacturerId());
diff --git a/core/tests/hosttests/test-apps/SharedUid/dual/Android.mk b/core/tests/hosttests/test-apps/SharedUid/dual/Android.mk
index a7dc1bd..60af2b9 100644
--- a/core/tests/hosttests/test-apps/SharedUid/dual/Android.mk
+++ b/core/tests/hosttests/test-apps/SharedUid/dual/Android.mk
@@ -34,6 +34,26 @@
LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_MANIFEST_FILE := dual/AndroidManifest.xml
+
+LOCAL_SDK_VERSION := current
+include $(BUILD_PACKAGE)
+
+LOCAL_PATH:= $(TOP_LOCAL_PATH)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := PMTest_Java_multiarch
+LOCAL_MULTILIB := both
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MANIFEST_FILE := multiarch/AndroidManifest.xml
+
+LOCAL_JNI_SHARED_LIBRARIES = libpmtestdual
+
+LOCAL_PROGUARD_ENABLED := disabled
+
LOCAL_SDK_VERSION := current
include $(BUILD_PACKAGE)
diff --git a/core/tests/hosttests/test-apps/SharedUid/dual/AndroidManifest.xml b/core/tests/hosttests/test-apps/SharedUid/dual/dual/AndroidManifest.xml
similarity index 100%
rename from core/tests/hosttests/test-apps/SharedUid/dual/AndroidManifest.xml
rename to core/tests/hosttests/test-apps/SharedUid/dual/dual/AndroidManifest.xml
diff --git a/core/tests/hosttests/test-apps/SharedUid/dual/multiarch/AndroidManifest.xml b/core/tests/hosttests/test-apps/SharedUid/dual/multiarch/AndroidManifest.xml
new file mode 100644
index 0000000..d53ba50
--- /dev/null
+++ b/core/tests/hosttests/test-apps/SharedUid/dual/multiarch/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<!-- This is an example of writing an application that bundles a
+ native code library. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.framework.shareduid.multiarch" android:sharedUserId="com.framework.shareduid">
+ <application android:label="Main Activity (multiarch)" android:multiArch="true">
+ <activity android:name="com.framework.shareduid.dual.MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java
index 5bc36fa..323a360 100644
--- a/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java
+++ b/core/tests/inputmethodtests/src/android/os/InputMethodSubtypeTest.java
@@ -21,26 +21,59 @@
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+import java.util.Objects;
+
public class InputMethodSubtypeTest extends InstrumentationTestCase {
- @SmallTest
- public void testLocale() throws Exception {
- // Compare locale
- assertEquals(createDummySubtype("en_US").getLocale(),
- createDummySubtype("en_US").getLocale());
- assertEquals(createDummySubtype("en_US").getLocale(),
- cloneViaParcel(createDummySubtype("en_US")).getLocale());
- assertEquals(createDummySubtype("en_US").getLocale(),
- cloneViaParcel(cloneViaParcel(createDummySubtype("en_US"))).getLocale());
+
+ public void verifyLocale(final String localeString) {
+ // InputMethodSubtype#getLocale() returns exactly the same string that is passed to the
+ // constructor.
+ assertEquals(localeString, createDummySubtype(localeString).getLocale());
+
+ // InputMethodSubtype#getLocale() should be preserved via marshaling.
+ assertEquals(createDummySubtype(localeString).getLocale(),
+ cloneViaParcel(createDummySubtype(localeString)).getLocale());
+
+ // InputMethodSubtype#getLocale() should be preserved via marshaling.
+ assertEquals(createDummySubtype(localeString).getLocale(),
+ cloneViaParcel(cloneViaParcel(createDummySubtype(localeString))).getLocale());
+
+ // Make sure InputMethodSubtype#hashCode() returns the same hash code.
+ assertEquals(createDummySubtype(localeString).hashCode(),
+ createDummySubtype(localeString).hashCode());
+ assertEquals(createDummySubtype(localeString).hashCode(),
+ cloneViaParcel(createDummySubtype(localeString)).hashCode());
+ assertEquals(createDummySubtype(localeString).hashCode(),
+ cloneViaParcel(cloneViaParcel(createDummySubtype(localeString))).hashCode());
}
@SmallTest
- public void testHashCode() throws Exception {
- assertEquals(createDummySubtype("en_US").hashCode(),
- createDummySubtype("en_US").hashCode());
- assertEquals(createDummySubtype("en_US").hashCode(),
- cloneViaParcel(createDummySubtype("en_US")).hashCode());
- assertEquals(createDummySubtype("en_US").hashCode(),
- cloneViaParcel(cloneViaParcel(createDummySubtype("en_US"))).hashCode());
+ public void testLocaleString() throws Exception {
+ // The locale string in InputMethodSubtype has accepted an arbitrary text actually,
+ // regardless of the validity of the text as a locale string.
+ verifyLocale("en_US");
+ verifyLocale("apparently invalid locale string");
+ verifyLocale("zz");
+ verifyLocale("iw");
+ verifyLocale("he");
+ }
+
+ @SmallTest
+ public void testDeprecatedLocaleString() throws Exception {
+ // Make sure "iw" is not automatically replaced with "he".
+ final InputMethodSubtype subtypeIw = createDummySubtype("iw");
+ final InputMethodSubtype subtypeHe = createDummySubtype("he");
+ assertEquals("iw", subtypeIw.getLocale());
+ assertEquals("he", subtypeHe.getLocale());
+ assertFalse(Objects.equals(subtypeIw, subtypeHe));
+ assertFalse(Objects.equals(subtypeIw.hashCode(), subtypeHe.hashCode()));
+
+ final InputMethodSubtype clonedSubtypeIw = cloneViaParcel(subtypeIw);
+ final InputMethodSubtype clonedSubtypeHe = cloneViaParcel(subtypeHe);
+ assertEquals(subtypeIw, clonedSubtypeIw);
+ assertEquals(subtypeHe, clonedSubtypeHe);
+ assertEquals("iw", clonedSubtypeIw.getLocale());
+ assertEquals("he", clonedSubtypeHe.getLocale());
}
private static final InputMethodSubtype cloneViaParcel(final InputMethodSubtype original) {
diff --git a/docs/html/images/ui/sample-linearlayout.png b/docs/html/images/ui/sample-linearlayout.png
index c4beb93a..04c9259 100644
--- a/docs/html/images/ui/sample-linearlayout.png
+++ b/docs/html/images/ui/sample-linearlayout.png
Binary files differ
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index 6870ab4..936fe0c 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -69,12 +69,17 @@
/**
* Set the shader's local matrix. Passing null will reset the shader's
- * matrix to identity
+ * matrix to identity.
+ *
+ * Starting with {@link android.os.Build.VERSION_CODES#L}, this does not
+ * modify any Paints which use this Shader. In order to modify the Paint,
+ * you need to call {@link Paint#setShader} again.
+ *
* @param localM The shader's new local matrix, or null to specify identity
*/
public void setLocalMatrix(Matrix localM) {
mLocalMatrix = localM;
- nativeSetLocalMatrix(native_instance,
+ native_instance = nativeSetLocalMatrix(native_instance,
localM == null ? 0 : localM.native_instance);
}
@@ -109,6 +114,6 @@
}
private static native void nativeDestructor(long native_shader);
- private static native void nativeSetLocalMatrix(long native_shader,
+ private static native long nativeSetLocalMatrix(long native_shader,
long matrix_instance);
}
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index 83a8ed5..db5c8e3 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -467,10 +467,12 @@
if (needMirroring()) {
updateMirrorMatrix(bounds.right - bounds.left);
shader.setLocalMatrix(mMirrorMatrix);
+ mBitmapState.mPaint.setShader(shader);
} else {
if (mMirrorMatrix != null) {
mMirrorMatrix = null;
shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
+ mBitmapState.mPaint.setShader(shader);
}
}
}
@@ -547,10 +549,12 @@
// Mirror the bitmap
updateMirrorMatrix(mDstRect.right - mDstRect.left);
shader.setLocalMatrix(mMirrorMatrix);
+ paint.setShader(shader);
} else {
if (mMirrorMatrix != null) {
mMirrorMatrix = null;
shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
+ paint.setShader(shader);
}
}
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 87f5989..4f05313 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -153,25 +153,6 @@
}
/**
- * @hide TO BE REMOVED FOR L-RELEASE
- */
- public RippleDrawable(Drawable content, Drawable mask) {
- this(new RippleState(null, null, null), null, null);
-
- if (content != null) {
- addLayer(content, null, 0, 0, 0, 0, 0);
- }
-
- if (mask != null) {
- addLayer(content, null, android.R.id.mask, 0, 0, 0, 0);
- }
-
- ensurePadding();
-
- Log.e(LOG_TAG, "This constructor is being removed", new RuntimeException());
- }
-
- /**
* Creates a new ripple drawable with the specified ripple color and
* optional content and mask drawables.
*
@@ -192,7 +173,7 @@
}
if (mask != null) {
- addLayer(content, null, android.R.id.mask, 0, 0, 0, 0);
+ addLayer(mask, null, android.R.id.mask, 0, 0, 0, 0);
}
setColor(color);
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 7789358..ba5594a 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -306,7 +306,7 @@
status_t DisplayListRenderer::drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, const SkPaint* paint) {
- if (fabs(sweepAngle) > 360.0f) {
+ if (fabs(sweepAngle) >= 360.0f) {
return drawOval(left, top, right, bottom, paint);
}
diff --git a/location/java/android/location/GpsClock.java b/location/java/android/location/GpsClock.java
new file mode 100644
index 0000000..f327c4e
--- /dev/null
+++ b/location/java/android/location/GpsClock.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class containing a GPS clock timestamp.
+ * It represents a measurement of the GPS receiver's clock.
+ *
+ * @hide
+ */
+public class GpsClock implements Parcelable {
+ // mandatory parameters
+ private long mTimeInNs;
+
+ // optional parameters
+ private boolean mHasLeapSecond;
+ private short mLeapSecond;
+ private boolean mHasTimeUncertaintyInNs;
+ private double mTimeUncertaintyInNs;
+ private boolean mHasBiasInNs;
+ private double mBiasInNs;
+ private boolean mHasBiasUncertaintyInNs;
+ private double mBiasUncertaintyInNs;
+ private boolean mHasDriftInNsPerSec;
+ private double mDriftInNsPerSec;
+ private boolean mHasDriftUncertaintyInNsPerSec;
+ private double mDriftUncertaintyInNsPerSec;
+
+ GpsClock() {
+ reset();
+ }
+
+ /**
+ * Sets all contents to the values stored in the provided object.
+ */
+ public void set(GpsClock clock) {
+ mTimeInNs = clock.mTimeInNs;
+
+ mHasLeapSecond = clock.mHasLeapSecond;
+ mLeapSecond = clock.mLeapSecond;
+ mHasTimeUncertaintyInNs = clock.mHasTimeUncertaintyInNs;
+ mTimeUncertaintyInNs = clock.mTimeUncertaintyInNs;
+ mHasBiasInNs = clock.mHasBiasInNs;
+ mBiasInNs = clock.mBiasInNs;
+ mHasBiasUncertaintyInNs = clock.mHasBiasUncertaintyInNs;
+ mBiasUncertaintyInNs = clock.mBiasUncertaintyInNs;
+ mHasDriftInNsPerSec = clock.mHasDriftInNsPerSec;
+ mDriftInNsPerSec = clock.mDriftInNsPerSec;
+ mHasDriftUncertaintyInNsPerSec = clock.mHasDriftUncertaintyInNsPerSec;
+ mDriftUncertaintyInNsPerSec = clock.mDriftUncertaintyInNsPerSec;
+ }
+
+ /**
+ * Resets all the contents to its original state.
+ */
+ public void reset() {
+ mTimeInNs = Long.MIN_VALUE;
+
+ resetLeapSecond();
+ resetTimeUncertaintyInNs();
+ resetBiasInNs();
+ resetBiasUncertaintyInNs();
+ resetDriftInNsPerSec();
+ resetDriftUncertaintyInNsPerSec();
+ }
+
+ /**
+ * Returns true if {@link #getLeapSecond()} is available, false otherwise.
+ */
+ public boolean hasLeapSecond() {
+ return mHasLeapSecond;
+ }
+
+ /**
+ * Gets the leap second associated with the clock's time.
+ *
+ * The value is only available if {@link #hasLeapSecond()} is true.
+ */
+ public short getLeapSecond() {
+ return mLeapSecond;
+ }
+
+ /**
+ * Sets the leap second associated with the clock's time.
+ */
+ public void setLeapSecond(short leapSecond) {
+ mHasLeapSecond = true;
+ mLeapSecond = leapSecond;
+ }
+
+ /**
+ * Resets the leap second associated with the clock's time.
+ */
+ public void resetLeapSecond() {
+ mHasLeapSecond = false;
+ mLeapSecond = Short.MIN_VALUE;
+ }
+
+ /**
+ * Gets the GPS clock Time in nanoseconds; it represents the uncorrected receiver's GPS time
+ * since 0000Z, January 6, 1980; this is, including {@link #getBiasInNs()}.
+ * The reported time includes {@link #getTimeUncertaintyInNs()}.
+ */
+ public long getTimeInNs() {
+ return mTimeInNs;
+ }
+
+ /**
+ * Sets the GPS clock Time in nanoseconds.
+ */
+ public void setTimeInNs(long timeInNs) {
+ mTimeInNs = timeInNs;
+ }
+
+ /**
+ * Returns true if {@link #getTimeUncertaintyInNs()} is available, false otherwise.
+ */
+ public boolean hasTimeUncertaintyInNs() {
+ return mHasTimeUncertaintyInNs;
+ }
+
+ /**
+ * Gets the clock's time Uncertainty (1-Sigma) in nanoseconds.
+ *
+ * The value is only available if {@link #hasTimeUncertaintyInNs()} is true.
+ */
+ public double getTimeUncertaintyInNs() {
+ return mTimeUncertaintyInNs;
+ }
+
+ /**
+ * Sets the clock's Time Uncertainty (1-Sigma) in nanoseconds.
+ */
+ public void setTimeUncertaintyInNs(double timeUncertaintyInNs) {
+ mHasTimeUncertaintyInNs = true;
+ mTimeUncertaintyInNs = timeUncertaintyInNs;
+ }
+
+ /**
+ * Resets the clock's Time Uncertainty (1-Sigma) in nanoseconds.
+ */
+ public void resetTimeUncertaintyInNs() {
+ mHasTimeUncertaintyInNs = false;
+ mTimeUncertaintyInNs = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getBiasInNs()} is available, false otherwise.
+ */
+ public boolean hasBiasInNs() {
+ return mHasBiasInNs;
+ }
+
+ /**
+ * Gets the clock's Bias in nanoseconds.
+ * The sign of the value (if available), is defined by the following equation:
+ * true time = time - bias.
+ * The reported bias includes {@link #getBiasUncertaintyInNs()}.
+ *
+ * The value is only available if {@link #hasBiasInNs()} is true.
+ */
+ public Double getBiasInNs() {
+ return mBiasInNs;
+ }
+
+ /**
+ * Sets the clock's Bias in nanoseconds.
+ */
+ public void setBiasInNs(double biasInNs) {
+ mHasBiasInNs = true;
+ mBiasInNs = biasInNs;
+ }
+
+ /**
+ * Resets the clock's Bias in nanoseconds.
+ */
+ public void resetBiasInNs() {
+ mHasBiasInNs = false;
+ mBiasInNs = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getBiasUncertaintyInNs()} is available, false otherwise.
+ */
+ public boolean hasBiasUncertaintyInNs() {
+ return mHasBiasUncertaintyInNs;
+ }
+
+ /**
+ * Gets the clock's Bias Uncertainty (1-Sigma) in nanoseconds.
+ *
+ * The value is only available if {@link #hasBiasUncertaintyInNs()} is true.
+ */
+ public double getBiasUncertaintyInNs() {
+ return mBiasUncertaintyInNs;
+ }
+
+ /**
+ * Sets the clock's Bias Uncertainty (1-Sigma) in nanoseconds.
+ */
+ public void setBiasUncertaintyInNs(double biasUncertaintyInNs) {
+ mHasBiasUncertaintyInNs = true;
+ mBiasUncertaintyInNs = biasUncertaintyInNs;
+ }
+
+ /**
+ * Resets the clock's Bias Uncertainty (1-Sigma) in nanoseconds.
+ */
+ public void resetBiasUncertaintyInNs() {
+ mHasBiasUncertaintyInNs = false;
+ mBiasUncertaintyInNs = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getDriftInNsPerSec()} is available, false otherwise.
+ */
+ public boolean hasDriftInNsPerSec() {
+ return mHasDriftInNsPerSec;
+ }
+
+ /**
+ * Gets the clock's Drift in nanoseconds per second.
+ * A positive value indicates that the frequency is higher than the nominal frequency.
+ * The reported drift includes {@link #getDriftUncertaintyInNsPerSec()}.
+ *
+ * The value is only available if {@link #hasDriftInNsPerSec()} is true.
+ */
+ public double getDriftInNsPerSec() {
+ return mDriftInNsPerSec;
+ }
+
+ /**
+ * Sets the clock's Drift in nanoseconds per second.
+ */
+ public void setDriftInNsPerSec(double driftInNsPerSec) {
+ mHasDriftInNsPerSec = true;
+ mDriftInNsPerSec = driftInNsPerSec;
+ }
+
+ /**
+ * Resets the clock's Drift in nanoseconds per second.
+ */
+ public void resetDriftInNsPerSec() {
+ mHasDriftInNsPerSec = false;
+ mDriftInNsPerSec = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getDriftUncertaintyInNsPerSec()} is available, false otherwise.
+ */
+ public boolean hasDriftUncertaintyInNsPerSec() {
+ return mHasDriftUncertaintyInNsPerSec;
+ }
+
+ /**
+ * Gets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second.
+ *
+ * The value is only available if {@link #hasDriftUncertaintyInNsPerSec()} is true.
+ */
+ public double getDriftUncertaintyInNsPerSec() {
+ return mDriftUncertaintyInNsPerSec;
+ }
+
+ /**
+ * Sets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second.
+ */
+ public void setDriftUncertaintyInNsPerSec(double driftUncertaintyInNsPerSec) {
+ mHasDriftUncertaintyInNsPerSec = true;
+ mDriftUncertaintyInNsPerSec = driftUncertaintyInNsPerSec;
+ }
+
+ /**
+ * Resets the clock's Drift Uncertainty (1-Sigma) in nanoseconds per second.
+ */
+ public void resetDriftUncertaintyInNsPerSec() {
+ mHasDriftUncertaintyInNsPerSec = false;
+ mDriftUncertaintyInNsPerSec = Double.NaN;
+ }
+
+ public static final Creator<GpsClock> CREATOR = new Creator<GpsClock>() {
+ @Override
+ public GpsClock createFromParcel(Parcel parcel) {
+ GpsClock gpsClock = new GpsClock();
+ gpsClock.mTimeInNs = parcel.readLong();
+
+ gpsClock.mHasLeapSecond = parcel.readInt() != 0;
+ gpsClock.mLeapSecond = (short) parcel.readInt();
+ gpsClock.mHasTimeUncertaintyInNs = parcel.readInt() != 0;
+ gpsClock.mTimeUncertaintyInNs = parcel.readDouble();
+ gpsClock.mHasBiasInNs = parcel.readInt() != 0;
+ gpsClock.mBiasInNs = parcel.readDouble();
+ gpsClock.mHasBiasUncertaintyInNs = parcel.readInt() != 0;
+ gpsClock.mBiasUncertaintyInNs = parcel.readDouble();
+ gpsClock.mHasDriftInNsPerSec = parcel.readInt() != 0;
+ gpsClock.mDriftInNsPerSec = parcel.readDouble();
+ gpsClock.mHasDriftUncertaintyInNsPerSec = parcel.readInt() != 0;
+ gpsClock.mDriftUncertaintyInNsPerSec = parcel.readDouble();
+
+ return gpsClock;
+ }
+
+ @Override
+ public GpsClock[] newArray(int size) {
+ return new GpsClock[size];
+ }
+ };
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeLong(mTimeInNs);
+
+ parcel.writeInt(mHasLeapSecond ? 1 : 0);
+ parcel.writeInt(mLeapSecond);
+ parcel.writeInt(mHasTimeUncertaintyInNs ? 1 : 0);
+ parcel.writeDouble(mTimeUncertaintyInNs);
+ parcel.writeInt(mHasBiasInNs ? 1 : 0);
+ parcel.writeDouble(mBiasInNs);
+ parcel.writeInt(mHasBiasUncertaintyInNs ? 1 : 0);
+ parcel.writeDouble(mBiasUncertaintyInNs);
+ parcel.writeInt(mHasDriftInNsPerSec ? 1 : 0);
+ parcel.writeDouble(mDriftInNsPerSec);
+ parcel.writeInt(mHasDriftUncertaintyInNsPerSec ? 1 : 0);
+ parcel.writeDouble(mDriftUncertaintyInNsPerSec);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ final String format = " %-15s = %-25s %-26s = %s\n";
+ StringBuilder builder = new StringBuilder("GpsClock:\n");
+
+ builder.append(String.format(
+ format,
+ "LeapSecond",
+ mHasLeapSecond ? mLeapSecond : null,
+ "",
+ ""));
+
+ builder.append(String.format(
+ format,
+ "TimeInNs",
+ mTimeInNs,
+ "TimeUncertaintyInNs",
+ mHasTimeUncertaintyInNs ? mTimeUncertaintyInNs : null));
+
+ builder.append(String.format(
+ format,
+ "BiasInNs",
+ mHasBiasInNs ? mBiasInNs : null,
+ "BiasUncertaintyInNs",
+ mHasBiasUncertaintyInNs ? mBiasUncertaintyInNs : null));
+
+ builder.append(String.format(
+ format,
+ "DriftInNsPerSec",
+ mHasDriftInNsPerSec ? mDriftInNsPerSec : null,
+ "DriftUncertaintyInNsPerSec",
+ mHasDriftUncertaintyInNsPerSec ? mDriftUncertaintyInNsPerSec : null));
+
+ return builder.toString();
+ }
+}
diff --git a/location/java/android/location/GpsMeasurement.java b/location/java/android/location/GpsMeasurement.java
new file mode 100644
index 0000000..e5a8c8c
--- /dev/null
+++ b/location/java/android/location/GpsMeasurement.java
@@ -0,0 +1,1222 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.location;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * A class representing a GPS satellite measurement, containing raw and computed information.
+ *
+ * @hide
+ */
+public class GpsMeasurement implements Parcelable {
+ private static final String TAG = "GpsMeasurement";
+
+ // mandatory parameters
+ private byte mPrn;
+ private long mLocalTimeInNs;
+ private long mReceivedGpsTowInNs;
+ private double mCn0InDbHz;
+ private double mPseudorangeRateInMetersPerSec;
+ private double mPseudorangeRateUncertaintyInMetersPerSec;
+ private double mAccumulatedDeltaRangeInMeters;
+ private double mAccumulatedDeltaRangeUncertaintyInMeters;
+
+ // optional parameters
+ private boolean mHasPseudorangeInMeters;
+ private double mPseudorangeInMeters;
+ private boolean mHasPseudorangeUncertaintyInMeters;
+ private double mPseudorangeUncertaintyInMeters;
+ private boolean mHasCodePhaseInChips;
+ private double mCodePhaseInChips;
+ private boolean mHasCodePhaseUncertaintyInChips;
+ private double mCodePhaseUncertaintyInChips;
+ private boolean mHasCarrierFrequencyInHz;
+ private float mCarrierFrequencyInHz;
+ private boolean mHasCarrierCycles;
+ private long mCarrierCycles;
+ private boolean mHasCarrierPhase;
+ private double mCarrierPhase;
+ private boolean mHasCarrierPhaseUncertainty;
+ private double mCarrierPhaseUncertainty;
+ private short mLossOfLock;
+ private boolean mHasBitNumber;
+ private short mBitNumber;
+ private boolean mHasTimeFromLastBitInNs;
+ private long mTimeFromLastBitInNs;
+ private boolean mHasDopplerShiftInHz;
+ private double mDopplerShiftInHz;
+ private boolean mHasDopplerShiftUncertaintyInHz;
+ private double mDopplerShiftUncertaintyInHz;
+ private short mMultipathIndicator;
+ private boolean mHasSnrInDb;
+ private double mSnrInDb;
+ private boolean mHasElevationInDeg;
+ private double mElevationInDeg;
+ private boolean mHasElevationUncertaintyInDeg;
+ private double mElevationUncertaintyInDeg;
+ private boolean mHasAzimuthInDeg;
+ private double mAzimuthInDeg;
+ private boolean mHasAzimuthUncertaintyInDeg;
+ private double mAzimuthUncertaintyInDeg;
+ private boolean mUsedInFix;
+
+ // The following enumerations must be in sync with the values declared in gps.h
+
+ /**
+ * The indicator is not available or it is unknown.
+ */
+ public static final short LOSS_OF_LOCK_UNKNOWN = 0;
+
+ /**
+ * The measurement does not present any indication of 'loss of lock'.
+ */
+ public static final short LOSS_OF_LOCK_OK = 1;
+
+ /**
+ * 'Loss of lock' detected between the previous and current observation: cycle slip possible.
+ */
+ public static final short LOSS_OF_LOCK_CYCLE_SLIP = 2;
+
+ /**
+ * The indicator is not available or it is unknown.
+ */
+ public static final short MULTIPATH_INDICATOR_UNKNOWN = 0;
+
+ /**
+ * The measurement has been indicated to use multi-path.
+ */
+ public static final short MULTIPATH_INDICATOR_DETECTED = 1;
+
+ /**
+ * The measurement has been indicated not tu use multi-path.
+ */
+ public static final short MULTIPATH_INDICATOR_NOT_USED = 2;
+
+ // End enumerations in sync with gps.h
+
+ GpsMeasurement() {
+ reset();
+ }
+
+ /**
+ * Sets all contents to the values stored in the provided object.
+ */
+ public void set(GpsMeasurement measurement) {
+ mPrn = measurement.mPrn;
+ mLocalTimeInNs = measurement.mLocalTimeInNs;
+ mReceivedGpsTowInNs = measurement.mReceivedGpsTowInNs;
+ mCn0InDbHz = measurement.mCn0InDbHz;
+ mPseudorangeRateInMetersPerSec = measurement.mPseudorangeRateInMetersPerSec;
+ mPseudorangeRateUncertaintyInMetersPerSec =
+ measurement.mPseudorangeRateUncertaintyInMetersPerSec;
+ mAccumulatedDeltaRangeInMeters = measurement.mAccumulatedDeltaRangeInMeters;
+ mAccumulatedDeltaRangeUncertaintyInMeters =
+ measurement.mAccumulatedDeltaRangeUncertaintyInMeters;
+
+ mHasPseudorangeInMeters = measurement.mHasPseudorangeInMeters;
+ mPseudorangeInMeters = measurement.mPseudorangeInMeters;
+ mHasPseudorangeUncertaintyInMeters = measurement.mHasPseudorangeUncertaintyInMeters;
+ mPseudorangeUncertaintyInMeters = measurement.mPseudorangeUncertaintyInMeters;
+ mHasCodePhaseInChips = measurement.mHasCodePhaseInChips;
+ mCodePhaseInChips = measurement.mCodePhaseInChips;
+ mHasCodePhaseUncertaintyInChips = measurement.mHasCodePhaseUncertaintyInChips;
+ mCodePhaseUncertaintyInChips = measurement.mCodePhaseUncertaintyInChips;
+ mHasCarrierFrequencyInHz = measurement.mHasCarrierFrequencyInHz;
+ mCarrierFrequencyInHz = measurement.mCarrierFrequencyInHz;
+ mHasCarrierCycles = measurement.mHasCarrierCycles;
+ mCarrierCycles = measurement.mCarrierCycles;
+ mHasCarrierPhase = measurement.mHasCarrierPhase;
+ mCarrierPhase = measurement.mCarrierPhase;
+ mHasCarrierPhaseUncertainty = measurement.mHasCarrierPhaseUncertainty;
+ mCarrierPhaseUncertainty = measurement.mCarrierPhaseUncertainty;
+ mLossOfLock = measurement.mLossOfLock;
+ mHasBitNumber = measurement.mHasBitNumber;
+ mBitNumber = measurement.mBitNumber;
+ mHasTimeFromLastBitInNs = measurement.mHasTimeFromLastBitInNs;
+ mTimeFromLastBitInNs = measurement.mTimeFromLastBitInNs;
+ mHasDopplerShiftInHz = measurement.mHasDopplerShiftInHz;
+ mDopplerShiftInHz = measurement.mDopplerShiftInHz;
+ mHasDopplerShiftUncertaintyInHz = measurement.mHasDopplerShiftUncertaintyInHz;
+ mDopplerShiftUncertaintyInHz = measurement.mDopplerShiftUncertaintyInHz;
+ mMultipathIndicator = measurement.mMultipathIndicator;
+ mHasSnrInDb = measurement.mHasSnrInDb;
+ mSnrInDb = measurement.mSnrInDb;
+ mHasElevationInDeg = measurement.mHasElevationInDeg;
+ mElevationInDeg = measurement.mElevationInDeg;
+ mHasElevationUncertaintyInDeg = measurement.mHasElevationUncertaintyInDeg;
+ mElevationUncertaintyInDeg = measurement.mElevationUncertaintyInDeg;
+ mHasAzimuthInDeg = measurement.mHasAzimuthInDeg;
+ mAzimuthInDeg = measurement.mAzimuthInDeg;
+ mHasAzimuthUncertaintyInDeg = measurement.mHasAzimuthUncertaintyInDeg;
+ mAzimuthUncertaintyInDeg = measurement.mAzimuthUncertaintyInDeg;
+ mUsedInFix = measurement.mUsedInFix;
+ }
+
+ /**
+ * Resets all the contents to its original state.
+ */
+ public void reset() {
+ mPrn = Byte.MIN_VALUE;
+ mLocalTimeInNs = Long.MIN_VALUE;
+ mReceivedGpsTowInNs = Long.MIN_VALUE;
+ mCn0InDbHz = Double.MIN_VALUE;
+ mPseudorangeRateInMetersPerSec = Double.MIN_VALUE;
+ mPseudorangeRateUncertaintyInMetersPerSec = Double.MIN_VALUE;
+ mAccumulatedDeltaRangeInMeters = Double.MIN_VALUE;
+ mAccumulatedDeltaRangeUncertaintyInMeters = Double.MIN_VALUE;
+
+ resetPseudorangeInMeters();
+ resetPseudorangeUncertaintyInMeters();
+ resetCodePhaseInChips();
+ resetCodePhaseUncertaintyInChips();
+ resetCarrierFrequencyInHz();
+ resetCarrierCycles();
+ resetCarrierPhase();
+ resetCarrierPhaseUncertainty();
+ setLossOfLock(LOSS_OF_LOCK_UNKNOWN);
+ resetBitNumber();
+ resetTimeFromLastBitInNs();
+ resetDopplerShiftInHz();
+ resetDopplerShiftUncertaintyInHz();
+ setMultipathIndicator(MULTIPATH_INDICATOR_UNKNOWN);
+ resetSnrInDb();
+ resetElevationInDeg();
+ resetElevationUncertaintyInDeg();
+ resetAzimuthInDeg();
+ resetAzimuthUncertaintyInDeg();
+ setUsedInFix(false);
+ }
+
+ /**
+ * Gets the Pseudo-random number (PRN).
+ * Range: [1, 32]
+ */
+ public byte getPrn() {
+ return mPrn;
+ }
+
+ /**
+ * Sets the Pseud-random number (PRN).
+ */
+ public void setPrn(byte value) {
+ mPrn = value;
+ }
+
+ /**
+ * Gets the local (hardware) time at which the measurement was taken in nanoseconds.
+ */
+ public long getLocalTimeInNs() {
+ return mLocalTimeInNs;
+ }
+
+ /**
+ * Sets the measurement's local (hardware) time in nanoseconds.
+ */
+ public void setLocalTimeInNs(long value) {
+ mLocalTimeInNs = value;
+ }
+
+ /**
+ * Gets the received GPS Time-of-Week in nanoseconds.
+ * The value is relative to the beginning of the current GPS week.
+ */
+ public long getReceivedGpsTowInNs() {
+ return mReceivedGpsTowInNs;
+ }
+
+ /**
+ * Sets the received GPS time-of-week in nanoseconds.
+ */
+ public void setReceivedGpsTowInNs(long value) {
+ mReceivedGpsTowInNs = value;
+ }
+
+ /**
+ * Gets the Carrier-to-noise density in dB-Hz.
+ * Range: [0, 63].
+ *
+ * The value contains the measured C/N0 for the signal at the antenna input.
+ */
+ public double getCn0InDbHz() {
+ return mCn0InDbHz;
+ }
+
+ /**
+ * Sets the carrier-to-noise density in dB-Hz.
+ */
+ public void setCn0InDbHz(double value) {
+ mCn0InDbHz = value;
+ }
+
+ /**
+ * Gets the Pseudorange rate at the timestamp in m/s.
+ * The reported value includes {@link #getPseudorangeRateUncertaintyInMetersPerSec()}.
+ */
+ public double getPseudorangeRateInMetersPerSec() {
+ return mPseudorangeRateInMetersPerSec;
+ }
+
+ /**
+ * Sets the pseudorange rate at the timestamp in m/s.
+ */
+ public void setPseudorangeRateInMetersPerSec(double value) {
+ mPseudorangeRateInMetersPerSec = value;
+ }
+
+ /**
+ * Gets the pseudorange's rate uncertainty (1-Sigma) in m/s.
+ * The uncertainty is represented as an absolute (single sided) value.
+ */
+ public double getPseudorangeRateUncertaintyInMetersPerSec() {
+ return mPseudorangeRateUncertaintyInMetersPerSec;
+ }
+
+ /**
+ * Sets the pseudorange's rate uncertainty (1-Sigma) in m/s.
+ */
+ public void setPseudorangeRateUncertaintyInMetersPerSec(double value) {
+ mPseudorangeRateUncertaintyInMetersPerSec = value;
+ }
+
+ /**
+ * Gets the accumulated delta range since the last channel reset, in meters.
+ * The reported value includes {@link #getAccumulatedDeltaRangeUncertaintyInMeters()}.
+ */
+ public double getAccumulatedDeltaRangeInMeters() {
+ return mAccumulatedDeltaRangeInMeters;
+ }
+
+ /**
+ * Sets the accumulated delta range in meters.
+ */
+ public void setAccumulatedDeltaRangeInMeters(double value) {
+ mAccumulatedDeltaRangeInMeters = value;
+ }
+
+ /**
+ * Gets the accumulated delta range's uncertainty (1-Sigma) in meters.
+ * The uncertainty is represented as an absolute (single sided) value.
+ */
+ public double getAccumulatedDeltaRangeUncertaintyInMeters() {
+ return mAccumulatedDeltaRangeUncertaintyInMeters;
+ }
+
+ /**
+ * Sets the accumulated delta range's uncertainty (1-sigma) in meters.
+ */
+ public void setAccumulatedDeltaRangeUncertaintyInMeters(double value) {
+ mAccumulatedDeltaRangeUncertaintyInMeters = value;
+ }
+
+ /**
+ * Returns true if {@link #getPseudorangeInMeters()} is available, false otherwise.
+ */
+ public boolean hasPseudorangeInMeters() {
+ return mHasPseudorangeInMeters;
+ }
+
+ /**
+ * Gets the best derived pseudorange by the chipset, in meters.
+ * The reported pseudorange includes {@link #getPseudorangeUncertaintyInMeters()}.
+ *
+ * The value is only available if {@link #hasPseudorangeInMeters()} is true.
+ */
+ public double getPseudorangeInMeters() {
+ return mPseudorangeInMeters;
+ }
+
+ /**
+ * Sets the Pseudo-range in meters.
+ */
+ public void setPseudorangeInMeters(double value) {
+ mHasPseudorangeInMeters = true;
+ mPseudorangeInMeters = value;
+ }
+
+ /**
+ * Resets the Pseudo-range in meters.
+ */
+ public void resetPseudorangeInMeters() {
+ mHasPseudorangeInMeters = false;
+ mPseudorangeInMeters = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getPseudorangeUncertaintyInMeters()} is available, false otherwise.
+ */
+ public boolean hasPseudorangeUncertaintyInMeters() {
+ return mHasPseudorangeUncertaintyInMeters;
+ }
+
+ /**
+ * Gets the pseudorange's uncertainty (1-Sigma) in meters.
+ * The value contains the 'pseudorange' and 'clock' uncertainty in it.
+ * The uncertainty is represented as an absolute (single sided) value.
+ *
+ * The value is only available if {@link #hasPseudorangeUncertaintyInMeters()} is true.
+ */
+ public double getPseudorangeUncertaintyInMeters() {
+ return mPseudorangeUncertaintyInMeters;
+ }
+
+ /**
+ * Sets the pseudo-range's uncertainty (1-Sigma) in meters.
+ */
+ public void setPseudorangeUncertaintyInMeters(double value) {
+ mHasPseudorangeUncertaintyInMeters = true;
+ mPseudorangeUncertaintyInMeters = value;
+ }
+
+ /**
+ * Resets the pseudo-range's uncertainty (1-Sigma) in meters.
+ */
+ public void resetPseudorangeUncertaintyInMeters() {
+ mHasPseudorangeUncertaintyInMeters = false;
+ mPseudorangeUncertaintyInMeters = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getCodePhaseInChips()} is available, false otherwise.
+ */
+ public boolean hasCodePhaseInChips() {
+ return mHasCodePhaseInChips;
+ }
+
+ /**
+ * Gets the fraction of the current C/A code cycle.
+ * Range: [0, 1023]
+ * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}.
+ * The reported code-phase includes {@link #getCodePhaseUncertaintyInChips()}.
+ *
+ * The value is only available if {@link #hasCodePhaseInChips()} is true.
+ */
+ public double getCodePhaseInChips() {
+ return mCodePhaseInChips;
+ }
+
+ /**
+ * Sets the Code-phase in chips.
+ */
+ public void setCodePhaseInChips(double value) {
+ mHasCodePhaseInChips = true;
+ mCodePhaseInChips = value;
+ }
+
+ /**
+ * Resets the Code-phase in chips.
+ */
+ public void resetCodePhaseInChips() {
+ mHasCodePhaseInChips = false;
+ mCodePhaseInChips = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getCodePhaseUncertaintyInChips()} is available, false otherwise.
+ */
+ public boolean hasCodePhaseUncertaintyInChips() {
+ return mHasCodePhaseUncertaintyInChips;
+ }
+
+ /**
+ * Gets the code-phase's uncertainty (1-Sigma) as a fraction of chips.
+ * The uncertainty is represented as an absolute (single sided) value.
+ *
+ * The value is only available if {@link #hasCodePhaseUncertaintyInChips()} is true.
+ */
+ public double getCodePhaseUncertaintyInChips() {
+ return mCodePhaseUncertaintyInChips;
+ }
+
+ /**
+ * Sets the Code-phase's uncertainty (1-Sigma) in fractions of chips.
+ */
+ public void setCodePhaseUncertaintyInChips(double value) {
+ mHasCodePhaseUncertaintyInChips = true;
+ mCodePhaseUncertaintyInChips = value;
+ }
+
+ /**
+ * Resets the Code-phase's uncertainty (1-Sigma) in fractions of chips.
+ */
+ public void resetCodePhaseUncertaintyInChips() {
+ mHasCodePhaseUncertaintyInChips = false;
+ mCodePhaseUncertaintyInChips = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getCarrierFrequencyInHz()} is available, false otherwise.
+ */
+ public boolean hasCarrierFrequencyInHz() {
+ return mHasCarrierFrequencyInHz;
+ }
+
+ /**
+ * Gets the carrier frequency at which codes and messages are modulated, it can be L1 or L2.
+ * If the field is not set, the carrier frequency corresponds to L1.
+ *
+ * The value is only available if {@link #hasCarrierFrequencyInHz()} is true.
+ */
+ public float getCarrierFrequencyInHz() {
+ return mCarrierFrequencyInHz;
+ }
+
+ /**
+ * Sets the Carrier frequency (L1 or L2) in Hz.
+ */
+ public void setCarrierFrequencyInHz(float carrierFrequencyInHz) {
+ mHasCarrierFrequencyInHz = true;
+ mCarrierFrequencyInHz = carrierFrequencyInHz;
+ }
+
+ /**
+ * Resets the Carrier frequency (L1 or L2) in Hz.
+ */
+ public void resetCarrierFrequencyInHz() {
+ mHasCarrierFrequencyInHz = false;
+ mCarrierFrequencyInHz = Float.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getCarrierCycles()} is available, false otherwise.
+ */
+ public boolean hasCarrierCycles() {
+ return mHasCarrierCycles;
+ }
+
+ /**
+ * The number of full carrier cycles between the satellite and the receiver.
+ * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}.
+ *
+ * The value is only available if {@link #hasCarrierCycles()} is true.
+ */
+ public long getCarrierCycles() {
+ return mCarrierCycles;
+ }
+
+ /**
+ * Sets the number of full carrier cycles between the satellite and the receiver.
+ */
+ public void setCarrierCycles(long value) {
+ mHasCarrierCycles = true;
+ mCarrierCycles = value;
+ }
+
+ /**
+ * Resets the number of full carrier cycles between the satellite and the receiver.
+ */
+ public void resetCarrierCycles() {
+ mHasCarrierCycles = false;
+ mCarrierCycles = Long.MIN_VALUE;
+ }
+
+ /**
+ * Returns true if {@link #getCarrierPhase()} is available, false otherwise.
+ */
+ public boolean hasCarrierPhase() {
+ return mHasCarrierPhase;
+ }
+
+ /**
+ * Gets the RF phase detected by the receiver.
+ * Range: [0.0, 1.0].
+ * This is usually the fractional part of the complete carrier phase measurement.
+ *
+ * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}.
+ * The reported carrier-phase includes {@link #getCarrierPhaseUncertainty()}.
+ *
+ * The value is only available if {@link #hasCarrierPhase()} is true.
+ */
+ public double getCarrierPhase() {
+ return mCarrierPhase;
+ }
+
+ /**
+ * Sets the RF phase detected by the receiver.
+ */
+ public void setCarrierPhase(double value) {
+ mHasCarrierPhase = true;
+ mCarrierPhase = value;
+ }
+
+ /**
+ * Resets the RF phase detected by the receiver.
+ */
+ public void resetCarrierPhase() {
+ mHasCarrierPhase = false;
+ mCarrierPhase = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getCarrierPhaseUncertainty()} is available, false otherwise.
+ */
+ public boolean hasCarrierPhaseUncertainty() {
+ return mHasCarrierPhaseUncertainty;
+ }
+
+ /**
+ * Gets the carrier-phase's uncertainty (1-Sigma).
+ * The uncertainty is represented as an absolute (single sided) value.
+ *
+ * The value is only available if {@link #hasCarrierPhaseUncertainty()} is true.
+ */
+ public double getCarrierPhaseUncertainty() {
+ return mCarrierPhaseUncertainty;
+ }
+
+ /**
+ * Sets the Carrier-phase's uncertainty (1-Sigma) in cycles.
+ */
+ public void setCarrierPhaseUncertainty(double value) {
+ mHasCarrierPhaseUncertainty = true;
+ mCarrierPhaseUncertainty = value;
+ }
+
+ /**
+ * Resets the Carrier-phase's uncertainty (1-Sigma) in cycles.
+ */
+ public void resetCarrierPhaseUncertainty() {
+ mHasCarrierPhaseUncertainty = false;
+ mCarrierPhaseUncertainty = Double.NaN;
+ }
+
+ /**
+ * Gets a value indicating the 'loss of lock' state of the event.
+ */
+ public short getLossOfLock() {
+ return mLossOfLock;
+ }
+
+ /**
+ * Sets the 'loss of lock' status.
+ */
+ public void setLossOfLock(short value) {
+ switch (value) {
+ case LOSS_OF_LOCK_UNKNOWN:
+ case LOSS_OF_LOCK_OK:
+ case LOSS_OF_LOCK_CYCLE_SLIP:
+ mLossOfLock = value;
+ break;
+ default:
+ Log.d(TAG, "Sanitizing invalid 'loss of lock': " + value);
+ mLossOfLock = LOSS_OF_LOCK_UNKNOWN;
+ break;
+ }
+ }
+
+ /**
+ * Gets a string representation of the 'loss of lock'.
+ * For internal and logging use only.
+ */
+ private String getLossOfLockString() {
+ switch (mLossOfLock) {
+ case LOSS_OF_LOCK_UNKNOWN:
+ return "Unknown";
+ case LOSS_OF_LOCK_OK:
+ return "Ok";
+ case LOSS_OF_LOCK_CYCLE_SLIP:
+ return "CycleSlip";
+ default:
+ return "Invalid";
+ }
+ }
+
+ /**
+ * Returns true if {@link #getBitNumber()} is available, false otherwise.
+ */
+ public boolean hasBitNumber() {
+ return mHasBitNumber;
+ }
+
+ /**
+ * Gets the number of GPS bits transmitted since Sat-Sun midnight (GPS week).
+ *
+ * The value is only available if {@link #hasBitNumber()} is true.
+ */
+ public short getBitNumber() {
+ return mBitNumber;
+ }
+
+ /**
+ * Sets the bit number within the broadcast frame.
+ */
+ public void setBitNumber(short bitNumber) {
+ mHasBitNumber = true;
+ mBitNumber = bitNumber;
+ }
+
+ /**
+ * Resets the bit number within the broadcast frame.
+ */
+ public void resetBitNumber() {
+ mHasBitNumber = false;
+ mBitNumber = Short.MIN_VALUE;
+ }
+
+ /**
+ * Returns true if {@link #getTimeFromLastBitInNs()} is available, false otherwise.
+ */
+ public boolean hasTimeFromLastBitInNs() {
+ return mHasTimeFromLastBitInNs;
+ }
+
+ /**
+ * Gets the elapsed time since the last received bit in nanoseconds.
+ * Range: [0, 20000000].
+ *
+ * The value is only available if {@link #hasTimeFromLastBitInNs()} is true.
+ */
+ public long getTimeFromLastBitInNs() {
+ return mTimeFromLastBitInNs;
+ }
+
+ /**
+ * Sets the elapsed time since the last received bit in nanoseconds.
+ */
+ public void setTimeFromLastBitInNs(long value) {
+ mHasTimeFromLastBitInNs = true;
+ mTimeFromLastBitInNs = value;
+ }
+
+ /**
+ * Resets the elapsed time since the last received bit in nanoseconds.
+ */
+ public void resetTimeFromLastBitInNs() {
+ mHasTimeFromLastBitInNs = false;
+ mTimeFromLastBitInNs = Long.MIN_VALUE;
+ }
+
+ /**
+ * Returns true if {@link #getDopplerShiftInHz()} is available, false otherwise.
+ */
+ public boolean hasDopplerShiftInHz() {
+ return mHasDopplerShiftInHz;
+ }
+
+ /**
+ * Gets the Doppler Shift in Hz.
+ * A positive value indicates that the SV is moving toward the receiver.
+ *
+ * The reference frequency is given by the value of {@link #getCarrierFrequencyInHz()}.
+ * The reported doppler shift includes {@link #getDopplerShiftUncertaintyInHz()}.
+ *
+ * The value is only available if {@link #hasDopplerShiftInHz()} is true.
+ */
+ public double getDopplerShiftInHz() {
+ return mDopplerShiftInHz;
+ }
+
+ /**
+ * Sets the Doppler shift in Hz.
+ */
+ public void setDopplerShiftInHz(double value) {
+ mHasDopplerShiftInHz = true;
+ mDopplerShiftInHz = value;
+ }
+
+ /**
+ * Resets the Doppler shift in Hz.
+ */
+ public void resetDopplerShiftInHz() {
+ mHasDopplerShiftInHz = false;
+ mDopplerShiftInHz = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getDopplerShiftUncertaintyInHz()} is available, false otherwise.
+ */
+ public boolean hasDopplerShiftUncertaintyInHz() {
+ return mHasDopplerShiftUncertaintyInHz;
+ }
+
+ /**
+ * Gets the Doppler's Shift uncertainty (1-Sigma) in Hz.
+ * The uncertainty is represented as an absolute (single sided) value.
+ *
+ * The value is only available if {@link #hasDopplerShiftUncertaintyInHz()} is true.
+ */
+ public double getDopplerShiftUncertaintyInHz() {
+ return mDopplerShiftUncertaintyInHz;
+ }
+
+ /**
+ * Sets the Doppler's shift uncertainty (1-Sigma) in Hz.
+ */
+ public void setDopplerShiftUncertaintyInHz(double value) {
+ mHasDopplerShiftUncertaintyInHz = true;
+ mDopplerShiftUncertaintyInHz = value;
+ }
+
+ /**
+ * Resets the Doppler's shift uncertainty (1-Sigma) in Hz.
+ */
+ public void resetDopplerShiftUncertaintyInHz() {
+ mHasDopplerShiftUncertaintyInHz = false;
+ mDopplerShiftUncertaintyInHz = Double.NaN;
+ }
+
+ /**
+ * Gets a value indicating the 'multipath' state of the event.
+ */
+ public short getMultipathIndicator() {
+ return mMultipathIndicator;
+ }
+
+ /**
+ * Sets the 'multi-path' indicator.
+ */
+ public void setMultipathIndicator(short value) {
+ switch (value) {
+ case MULTIPATH_INDICATOR_UNKNOWN:
+ case MULTIPATH_INDICATOR_DETECTED:
+ case MULTIPATH_INDICATOR_NOT_USED:
+ mMultipathIndicator = value;
+ break;
+ default:
+ Log.d(TAG, "Sanitizing invalid 'muti-path indicator': " + value);
+ mMultipathIndicator = MULTIPATH_INDICATOR_UNKNOWN;
+ break;
+ }
+ }
+
+ /**
+ * Gets a string representation of the 'multi-path indicator'.
+ * For internal and logging use only.
+ */
+ private String getMultipathIndicatorString() {
+ switch(mMultipathIndicator) {
+ case MULTIPATH_INDICATOR_UNKNOWN:
+ return "Unknown";
+ case MULTIPATH_INDICATOR_DETECTED:
+ return "Detected";
+ case MULTIPATH_INDICATOR_NOT_USED:
+ return "NotDetected";
+ default:
+ return "Invalid";
+ }
+ }
+
+ /**
+ * Returns true if {@link #getSnrInDb()} is available, false otherwise.
+ */
+ public boolean hasSnrInDb() {
+ return mHasSnrInDb;
+ }
+
+ /**
+ * Gets the Signal-to-Noise ratio (SNR) in dB.
+ *
+ * The value is only available if {@link #hasSnrInDb()} is true.
+ */
+ public double getSnrInDb() {
+ return mSnrInDb;
+ }
+
+ /**
+ * Sets the Signal-to-noise ratio (SNR) in dB.
+ */
+ public void setSnrInDb(double snrInDb) {
+ mHasSnrInDb = true;
+ mSnrInDb = snrInDb;
+ }
+
+ /**
+ * Resets the Signal-to-noise ratio (SNR) in dB.
+ */
+ public void resetSnrInDb() {
+ mHasSnrInDb = false;
+ mSnrInDb = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getElevationInDeg()} is available, false otherwise.
+ */
+ public boolean hasElevationInDeg() {
+ return mHasElevationInDeg;
+ }
+
+ /**
+ * Gets the Elevation in degrees.
+ * Range: [-90, 90]
+ * The reported elevation includes {@link #getElevationUncertaintyInDeg()}.
+ *
+ * The value is only available if {@link #hasElevationInDeg()} is true.
+ */
+ public double getElevationInDeg() {
+ return mElevationInDeg;
+ }
+
+ /**
+ * Sets the Elevation in degrees.
+ */
+ public void setElevationInDeg(double elevationInDeg) {
+ mHasElevationInDeg = true;
+ mElevationInDeg = elevationInDeg;
+ }
+
+ /**
+ * Resets the Elevation in degrees.
+ */
+ public void resetElevationInDeg() {
+ mHasElevationInDeg = false;
+ mElevationInDeg = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getElevationUncertaintyInDeg()} is available, false otherwise.
+ */
+ public boolean hasElevationUncertaintyInDeg() {
+ return mHasElevationUncertaintyInDeg;
+ }
+
+ /**
+ * Gets the elevation's uncertainty (1-Sigma) in degrees.
+ * Range: [0, 90]
+ *
+ * The uncertainty is represented as an absolute (single sided) value.
+ *
+ * The value is only available if {@link #hasElevationUncertaintyInDeg()} is true.
+ */
+ public double getElevationUncertaintyInDeg() {
+ return mElevationUncertaintyInDeg;
+ }
+
+ /**
+ * Sets the elevation's uncertainty (1-Sigma) in degrees.
+ */
+ public void setElevationUncertaintyInDeg(double value) {
+ mHasElevationUncertaintyInDeg = true;
+ mElevationUncertaintyInDeg = value;
+ }
+
+ /**
+ * Resets the elevation's uncertainty (1-Sigma) in degrees.
+ */
+ public void resetElevationUncertaintyInDeg() {
+ mHasElevationUncertaintyInDeg = false;
+ mElevationUncertaintyInDeg = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getAzimuthInDeg()} is available, false otherwise.
+ */
+ public boolean hasAzimuthInDeg() {
+ return mHasAzimuthInDeg;
+ }
+
+ /**
+ * Gets the azimuth in degrees.
+ * Range: [0, 360).
+ *
+ * The reported azimuth includes {@link #getAzimuthUncertaintyInDeg()}.
+ *
+ * The value is only available if {@link #hasAzimuthInDeg()} is true.
+ */
+ public double getAzimuthInDeg() {
+ return mAzimuthInDeg;
+ }
+
+ /**
+ * Sets the Azimuth in degrees.
+ */
+ public void setAzimuthInDeg(double value) {
+ mHasAzimuthInDeg = true;
+ mAzimuthInDeg = value;
+ }
+
+ /**
+ * Resets the Azimuth in degrees.
+ */
+ public void resetAzimuthInDeg() {
+ mHasAzimuthInDeg = false;
+ mAzimuthInDeg = Double.NaN;
+ }
+
+ /**
+ * Returns true if {@link #getAzimuthUncertaintyInDeg()} is available, false otherwise.
+ */
+ public boolean hasAzimuthUncertaintyInDeg() {
+ return mHasAzimuthUncertaintyInDeg;
+ }
+
+ /**
+ * Gets the azimuth's uncertainty (1-Sigma) in degrees.
+ * Range: [0, 180].
+ *
+ * The uncertainty is represented as an absolute (single sided) value.
+ *
+ * The value is only available if {@link #hasAzimuthUncertaintyInDeg()} is true.
+ */
+ public double getAzimuthUncertaintyInDeg() {
+ return mAzimuthUncertaintyInDeg;
+ }
+
+ /**
+ * Sets the Azimuth's uncertainty (1-Sigma) in degrees.
+ */
+ public void setAzimuthUncertaintyInDeg(double value) {
+ mHasAzimuthUncertaintyInDeg = true;
+ mAzimuthUncertaintyInDeg = value;
+ }
+
+ /**
+ * Resets the Azimuth's uncertainty (1-Sigma) in degrees.
+ */
+ public void resetAzimuthUncertaintyInDeg() {
+ mHasAzimuthUncertaintyInDeg = false;
+ mAzimuthUncertaintyInDeg = Double.NaN;
+ }
+
+ /**
+ * Gets a flag indicating whether the GPS represented by the measurement was used for computing
+ * the most recent fix.
+ *
+ * @return A non-null value if the data is available, null otherwise.
+ */
+ public Boolean isUsedInFix() {
+ return mUsedInFix;
+ }
+
+ /**
+ * Sets the Used-in-Fix flag.
+ */
+ public void setUsedInFix(boolean value) {
+ mUsedInFix = value;
+ }
+
+ public static final Creator<GpsMeasurement> CREATOR = new Creator<GpsMeasurement>() {
+ @Override
+ public GpsMeasurement createFromParcel(Parcel parcel) {
+ GpsMeasurement gpsMeasurement = new GpsMeasurement();
+
+ gpsMeasurement.mPrn = parcel.readByte();
+ gpsMeasurement.mLocalTimeInNs = parcel.readLong();
+ gpsMeasurement.mReceivedGpsTowInNs = parcel.readLong();
+ gpsMeasurement.mCn0InDbHz = parcel.readDouble();
+ gpsMeasurement.mPseudorangeRateInMetersPerSec = parcel.readDouble();
+ gpsMeasurement.mPseudorangeRateUncertaintyInMetersPerSec = parcel.readDouble();
+ gpsMeasurement.mAccumulatedDeltaRangeInMeters = parcel.readDouble();
+ gpsMeasurement.mAccumulatedDeltaRangeUncertaintyInMeters = parcel.readDouble();
+
+ gpsMeasurement.mHasPseudorangeInMeters = parcel.readInt() != 0;
+ gpsMeasurement.mPseudorangeInMeters = parcel.readDouble();
+ gpsMeasurement.mHasPseudorangeUncertaintyInMeters = parcel.readInt() != 0;
+ gpsMeasurement.mPseudorangeUncertaintyInMeters = parcel.readDouble();
+ gpsMeasurement.mHasCodePhaseInChips = parcel.readInt() != 0;
+ gpsMeasurement.mCodePhaseInChips = parcel.readDouble();
+ gpsMeasurement.mHasCodePhaseUncertaintyInChips = parcel.readInt() != 0;
+ gpsMeasurement.mCodePhaseUncertaintyInChips = parcel.readDouble();
+ gpsMeasurement.mHasCarrierFrequencyInHz = parcel.readInt() != 0;
+ gpsMeasurement.mCarrierFrequencyInHz = parcel.readFloat();
+ gpsMeasurement.mHasCarrierCycles = parcel.readInt() != 0;
+ gpsMeasurement.mCarrierCycles = parcel.readLong();
+ gpsMeasurement.mHasCarrierPhase = parcel.readInt() != 0;
+ gpsMeasurement.mCarrierPhase = parcel.readDouble();
+ gpsMeasurement.mHasCarrierPhaseUncertainty = parcel.readInt() != 0;
+ gpsMeasurement.mCarrierPhaseUncertainty = parcel.readDouble();
+ gpsMeasurement.mLossOfLock = (short) parcel.readInt();
+ gpsMeasurement.mHasBitNumber = parcel.readInt() != 0;
+ gpsMeasurement.mBitNumber = (short) parcel.readInt();
+ gpsMeasurement.mHasTimeFromLastBitInNs = parcel.readInt() != 0;
+ gpsMeasurement.mTimeFromLastBitInNs = parcel.readLong();
+ gpsMeasurement.mHasDopplerShiftInHz = parcel.readInt() != 0;
+ gpsMeasurement.mDopplerShiftInHz = parcel.readDouble();
+ gpsMeasurement.mHasDopplerShiftUncertaintyInHz = parcel.readInt() != 0;
+ gpsMeasurement.mDopplerShiftUncertaintyInHz = parcel.readDouble();
+ gpsMeasurement.mMultipathIndicator = (short) parcel.readInt();
+ gpsMeasurement.mHasSnrInDb = parcel.readInt() != 0;
+ gpsMeasurement.mSnrInDb = parcel.readDouble();
+ gpsMeasurement.mHasElevationInDeg = parcel.readInt() != 0;
+ gpsMeasurement.mElevationInDeg = parcel.readDouble();
+ gpsMeasurement.mHasElevationUncertaintyInDeg = parcel.readInt() != 0;
+ gpsMeasurement.mElevationUncertaintyInDeg = parcel.readDouble();
+ gpsMeasurement.mHasAzimuthInDeg = parcel.readInt() != 0;
+ gpsMeasurement.mAzimuthInDeg = parcel.readDouble();
+ gpsMeasurement.mHasAzimuthUncertaintyInDeg = parcel.readInt() != 0;
+ gpsMeasurement.mAzimuthUncertaintyInDeg = parcel.readDouble();
+ gpsMeasurement.mUsedInFix = parcel.readInt() != 0;
+
+ return gpsMeasurement;
+ }
+
+ @Override
+ public GpsMeasurement[] newArray(int i) {
+ return new GpsMeasurement[i];
+ }
+ };
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeByte(mPrn);
+ parcel.writeLong(mLocalTimeInNs);
+ parcel.writeLong(mReceivedGpsTowInNs);
+ parcel.writeDouble(mCn0InDbHz);
+ parcel.writeDouble(mPseudorangeRateInMetersPerSec);
+ parcel.writeDouble(mPseudorangeRateUncertaintyInMetersPerSec);
+ parcel.writeDouble(mAccumulatedDeltaRangeInMeters);
+ parcel.writeDouble(mAccumulatedDeltaRangeUncertaintyInMeters);
+
+ parcel.writeInt(mHasPseudorangeInMeters ? 1 : 0);
+ parcel.writeDouble(mPseudorangeInMeters);
+ parcel.writeInt(mHasPseudorangeUncertaintyInMeters ? 1 : 0);
+ parcel.writeDouble(mPseudorangeUncertaintyInMeters);
+ parcel.writeInt(mHasCodePhaseInChips ? 1 : 0);
+ parcel.writeDouble(mCodePhaseInChips);
+ parcel.writeInt(mHasCodePhaseUncertaintyInChips ? 1 : 0);
+ parcel.writeDouble(mCodePhaseUncertaintyInChips);
+ parcel.writeInt(mHasCarrierFrequencyInHz ? 1 : 0);
+ parcel.writeFloat(mCarrierFrequencyInHz);
+ parcel.writeInt(mHasCarrierCycles ? 1 : 0);
+ parcel.writeLong(mCarrierCycles);
+ parcel.writeInt(mHasCarrierPhase ? 1 : 0);
+ parcel.writeDouble(mCarrierPhase);
+ parcel.writeInt(mHasCarrierPhaseUncertainty ? 1 : 0);
+ parcel.writeDouble(mCarrierPhaseUncertainty);
+ parcel.writeInt(mLossOfLock);
+ parcel.writeInt(mHasBitNumber ? 1 : 0);
+ parcel.writeInt(mBitNumber);
+ parcel.writeInt(mHasTimeFromLastBitInNs ? 1 : 0);
+ parcel.writeLong(mTimeFromLastBitInNs);
+ parcel.writeInt(mHasDopplerShiftInHz ? 1 : 0);
+ parcel.writeDouble(mDopplerShiftInHz);
+ parcel.writeInt(mHasDopplerShiftUncertaintyInHz ? 1 : 0);
+ parcel.writeDouble(mDopplerShiftUncertaintyInHz);
+ parcel.writeInt(mMultipathIndicator);
+ parcel.writeInt(mHasSnrInDb ? 1 : 0);
+ parcel.writeDouble(mSnrInDb);
+ parcel.writeInt(mHasElevationInDeg ? 1 : 0);
+ parcel.writeDouble(mElevationInDeg);
+ parcel.writeInt(mHasElevationUncertaintyInDeg ? 1 : 0);
+ parcel.writeDouble(mElevationUncertaintyInDeg);
+ parcel.writeInt(mHasAzimuthInDeg ? 1 : 0);
+ parcel.writeDouble(mAzimuthInDeg);
+ parcel.writeInt(mHasAzimuthUncertaintyInDeg ? 1 : 0);
+ parcel.writeDouble(mAzimuthUncertaintyInDeg);
+ parcel.writeInt(mUsedInFix ? 1 : 0);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ final String format = " %-29s = %s\n";
+ final String formatWithUncertainty = " %-29s = %-25s %-40s = %s\n";
+ StringBuilder builder = new StringBuilder("GpsMeasurement:\n");
+
+ builder.append(String.format(format, "Prn", mPrn));
+
+ builder.append(String.format(format, "LocalTimeInNs", mLocalTimeInNs));
+
+ builder.append(String.format(format, "ReceivedGpsTowInNs", mReceivedGpsTowInNs));
+
+ builder.append(String.format(format, "Cn0InDbHz", mCn0InDbHz));
+
+ builder.append(String.format(
+ formatWithUncertainty,
+ "PseudorangeRateInMetersPerSec",
+ mPseudorangeRateInMetersPerSec,
+ "PseudorangeRateUncertaintyInMetersPerSec",
+ mPseudorangeRateUncertaintyInMetersPerSec));
+
+ builder.append(String.format(
+ formatWithUncertainty,
+ "AccumulatedDeltaRangeInMeters",
+ mAccumulatedDeltaRangeInMeters,
+ "AccumulatedDeltaRangeUncertaintyInMeters",
+ mAccumulatedDeltaRangeUncertaintyInMeters));
+
+
+ builder.append(String.format(
+ formatWithUncertainty,
+ "PseudorangeInMeters",
+ mHasPseudorangeInMeters ? mPseudorangeInMeters : null,
+ "PseudorangeUncertaintyInMeters",
+ mHasPseudorangeUncertaintyInMeters ? mPseudorangeUncertaintyInMeters : null));
+
+ builder.append(String.format(
+ formatWithUncertainty,
+ "CodePhaseInChips",
+ mHasCodePhaseInChips ? mCodePhaseInChips : null,
+ "CodePhaseUncertaintyInChips",
+ mHasCodePhaseUncertaintyInChips ? mCodePhaseUncertaintyInChips : null));
+
+ builder.append(String.format(
+ format,
+ "CarrierFrequencyInHz",
+ mHasCarrierFrequencyInHz ? mCarrierFrequencyInHz : null));
+
+ builder.append(String.format(
+ format,
+ "CarrierCycles",
+ mHasCarrierCycles ? mCarrierCycles : null));
+
+ builder.append(String.format(
+ formatWithUncertainty,
+ "CarrierPhase",
+ mHasCarrierPhase ? mCarrierPhase : null,
+ "CarrierPhaseUncertainty",
+ mHasCarrierPhaseUncertainty ? mCarrierPhaseUncertainty : null));
+
+ builder.append(String.format(format, "LossOfLock", getLossOfLockString()));
+
+ builder.append(String.format(
+ format,
+ "BitNumber",
+ mHasBitNumber ? mBitNumber : null));
+
+ builder.append(String.format(
+ format,
+ "TimeFromLastBitInNs",
+ mHasTimeFromLastBitInNs ? mTimeFromLastBitInNs : null));
+
+ builder.append(String.format(
+ formatWithUncertainty,
+ "DopplerShiftInHz",
+ mHasDopplerShiftInHz ? mDopplerShiftInHz : null,
+ "DopplerShiftUncertaintyInHz",
+ mHasDopplerShiftUncertaintyInHz ? mDopplerShiftUncertaintyInHz : null));
+
+ builder.append(String.format(format, "MultipathIndicator", getMultipathIndicatorString()));
+
+ builder.append(String.format(
+ format,
+ "SnrInDb",
+ mHasSnrInDb ? mSnrInDb : null));
+
+ builder.append(String.format(
+ formatWithUncertainty,
+ "ElevationInDeg",
+ mHasElevationInDeg ? mElevationInDeg : null,
+ "ElevationUncertaintyInDeg",
+ mHasElevationUncertaintyInDeg ? mElevationUncertaintyInDeg : null));
+
+ builder.append(String.format(
+ formatWithUncertainty,
+ "AzimuthInDeg",
+ mHasAzimuthInDeg ? mAzimuthInDeg : null,
+ "AzimuthUncertaintyInDeg",
+ mHasAzimuthUncertaintyInDeg ? mAzimuthUncertaintyInDeg : null));
+
+ builder.append(String.format(format, "UsedInFix", mUsedInFix));
+
+ return builder.toString();
+ }
+}
diff --git a/location/java/android/location/GpsMeasurementListenerTransport.java b/location/java/android/location/GpsMeasurementListenerTransport.java
new file mode 100644
index 0000000..48a4b44
--- /dev/null
+++ b/location/java/android/location/GpsMeasurementListenerTransport.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.location;
+
+import com.android.internal.util.Preconditions;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * A handler class to manage transport listeners for {@link GpsMeasurementsEvent.Listener},
+ * and post the events in a handler.
+ *
+ * @hide
+ */
+class GpsMeasurementListenerTransport {
+ private static final String TAG = "GpsMeasurementListenerTransport";
+
+ private final Context mContext;
+ private final ILocationManager mLocationManager;
+
+ private final IGpsMeasurementsListener mListenerTransport = new ListenerTransport();
+ private final HashSet<GpsMeasurementsEvent.Listener> mListeners =
+ new HashSet<GpsMeasurementsEvent.Listener>();
+
+ public GpsMeasurementListenerTransport(Context context, ILocationManager locationManager) {
+ mContext = context;
+ mLocationManager = locationManager;
+ }
+
+ public boolean add(@NonNull GpsMeasurementsEvent.Listener listener) {
+ Preconditions.checkNotNull(listener);
+
+ synchronized (mListeners) {
+ // we need to register with the service first, because we need to find out if the
+ // service will actually support the request before we attempt anything
+ if (mListeners.isEmpty()) {
+ boolean registeredWithServer;
+ try {
+ registeredWithServer = mLocationManager.addGpsMeasurementsListener(
+ mListenerTransport,
+ mContext.getPackageName());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error handling first listener.", e);
+ return false;
+ }
+
+ if (!registeredWithServer) {
+ Log.e(TAG, "Unable to register listener transport.");
+ return false;
+ }
+ }
+
+ if (mListeners.contains(listener)) {
+ return true;
+ }
+
+ mListeners.add(listener);
+ }
+
+ return true;
+ }
+
+ public void remove(@NonNull GpsMeasurementsEvent.Listener listener) {
+ Preconditions.checkNotNull(listener);
+
+ synchronized (mListeners) {
+ boolean removed = mListeners.remove(listener);
+
+ boolean isLastListener = removed && mListeners.isEmpty();
+ if (isLastListener) {
+ try {
+ mLocationManager.removeGpsMeasurementsListener(mListenerTransport);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error handling last listener.", e);
+ }
+ }
+ }
+ }
+
+ private class ListenerTransport extends IGpsMeasurementsListener.Stub {
+ @Override
+ public void onGpsMeasurementsReceived(final GpsMeasurementsEvent eventArgs) {
+ Collection<GpsMeasurementsEvent.Listener> listeners;
+ synchronized (mListeners) {
+ listeners = new ArrayList<GpsMeasurementsEvent.Listener>(mListeners);
+ }
+
+ for (final GpsMeasurementsEvent.Listener listener : listeners) {
+ listener.onGpsMeasurementsReceived(eventArgs);
+ }
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/location/java/android/location/GpsMeasurementsEvent.aidl
similarity index 75%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to location/java/android/location/GpsMeasurementsEvent.aidl
index 3da1321..2c46262 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/location/java/android/location/GpsMeasurementsEvent.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.bluetooth.le;
+package android.location;
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable GpsMeasurementsEvent;
diff --git a/location/java/android/location/GpsMeasurementsEvent.java b/location/java/android/location/GpsMeasurementsEvent.java
new file mode 100644
index 0000000..e04ed81
--- /dev/null
+++ b/location/java/android/location/GpsMeasurementsEvent.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.location;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.security.InvalidParameterException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * A class implementing a container for data associated with a measurement event.
+ * Events are delivered to registered instances of {@link Listener}.
+ *
+ * @hide
+ */
+public class GpsMeasurementsEvent implements Parcelable {
+ private final GpsClock mClock;
+ private final Collection<GpsMeasurement> mReadOnlyMeasurements;
+
+ /**
+ * Used for receiving GPS satellite measurements from the GPS engine.
+ * Each measurement contains raw and computed data identifying a satellite.
+ * You can implement this interface and call {@link LocationManager#addGpsMeasurementListener}.
+ *
+ * @hide
+ */
+ public interface Listener {
+ void onGpsMeasurementsReceived(GpsMeasurementsEvent eventArgs);
+ }
+
+ public GpsMeasurementsEvent(GpsClock clock, GpsMeasurement[] measurements) {
+ if (clock == null) {
+ throw new InvalidParameterException("Parameter 'clock' must not be null.");
+ }
+ if (measurements == null || measurements.length == 0) {
+ throw new InvalidParameterException(
+ "Parameter 'measurements' must not be null or empty.");
+ }
+
+ mClock = clock;
+ Collection<GpsMeasurement> measurementCollection = Arrays.asList(measurements);
+ mReadOnlyMeasurements = Collections.unmodifiableCollection(measurementCollection);
+ }
+
+ @NonNull
+ public GpsClock getClock() {
+ return mClock;
+ }
+
+ /**
+ * Gets a read-only collection of measurements associated with the current event.
+ */
+ @NonNull
+ public Collection<GpsMeasurement> getMeasurements() {
+ return mReadOnlyMeasurements;
+ }
+
+ public static final Creator<GpsMeasurementsEvent> CREATOR =
+ new Creator<GpsMeasurementsEvent>() {
+ @Override
+ public GpsMeasurementsEvent createFromParcel(Parcel in) {
+ ClassLoader classLoader = getClass().getClassLoader();
+
+ GpsClock clock = in.readParcelable(classLoader);
+
+ int measurementsLength = in.readInt();
+ GpsMeasurement[] measurementsArray = new GpsMeasurement[measurementsLength];
+ in.readTypedArray(measurementsArray, GpsMeasurement.CREATOR);
+
+ return new GpsMeasurementsEvent(clock, measurementsArray);
+ }
+
+ @Override
+ public GpsMeasurementsEvent[] newArray(int size) {
+ return new GpsMeasurementsEvent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeParcelable(mClock, flags);
+
+ GpsMeasurement[] measurementsArray = mReadOnlyMeasurements.toArray(new GpsMeasurement[0]);
+ parcel.writeInt(measurementsArray.length);
+ parcel.writeTypedArray(measurementsArray, flags);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("[ GpsMeasurementsEvent:\n\n");
+
+ builder.append(mClock.toString());
+ builder.append("\n");
+
+ for (GpsMeasurement measurement : mReadOnlyMeasurements) {
+ builder.append(measurement.toString());
+ builder.append("\n");
+ }
+
+ builder.append("]");
+
+ return builder.toString();
+ }
+}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/location/java/android/location/IGpsMeasurementsListener.aidl
similarity index 61%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to location/java/android/location/IGpsMeasurementsListener.aidl
index 3da1321..b34bb6c 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/location/java/android/location/IGpsMeasurementsListener.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2014, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,6 +14,13 @@
* limitations under the License.
*/
-package android.bluetooth.le;
+package android.location;
-parcelable AdvertisementData;
\ No newline at end of file
+import android.location.GpsMeasurementsEvent;
+
+/**
+ * {@hide}
+ */
+oneway interface IGpsMeasurementsListener {
+ void onGpsMeasurementsReceived(in GpsMeasurementsEvent event);
+}
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index c353ec6..a1acaf1 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -21,7 +21,7 @@
import android.location.Criteria;
import android.location.GeocoderParams;
import android.location.Geofence;
-import android.location.IGeocodeProvider;
+import android.location.IGpsMeasurementsListener;
import android.location.IGpsStatusListener;
import android.location.ILocationListener;
import android.location.Location;
@@ -60,6 +60,9 @@
boolean sendNiResponse(int notifId, int userResponse);
+ boolean addGpsMeasurementsListener(in IGpsMeasurementsListener listener, in String packageName);
+ boolean removeGpsMeasurementsListener(in IGpsMeasurementsListener listener);
+
// --- deprecated ---
List<String> getAllProviders();
List<String> getProviders(in Criteria criteria, boolean enabledOnly);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 4502a5b..d6a8fb8 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -16,25 +16,23 @@
package android.location;
+import com.android.internal.location.ProviderProperties;
+
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
-import android.os.Looper;
-import android.os.RemoteException;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
+import android.os.RemoteException;
import android.util.Log;
-
-import java.lang.SecurityException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import com.android.internal.location.ProviderProperties;
-
/**
* This class provides access to the system location services. These
* services allow applications to obtain periodic updates of the
@@ -60,6 +58,7 @@
private final Context mContext;
private final ILocationManager mService;
+ private final GpsMeasurementListenerTransport mGpsMeasurementListenerTransport;
private final HashMap<GpsStatus.Listener, GpsStatusListenerTransport> mGpsStatusListeners =
new HashMap<GpsStatus.Listener, GpsStatusListenerTransport>();
private final HashMap<GpsStatus.NmeaListener, GpsStatusListenerTransport> mNmeaListeners =
@@ -310,6 +309,7 @@
public LocationManager(Context context, ILocationManager service) {
mService = service;
mContext = context;
+ mGpsMeasurementListenerTransport = new GpsMeasurementListenerTransport(mContext, mService);
}
private LocationProvider createProvider(String name, ProviderProperties properties) {
@@ -1570,6 +1570,29 @@
}
}
+ /**
+ * Adds a GPS Measurement listener.
+ *
+ * @param listener a {@link android.location.GpsMeasurementsEvent.Listener} object to register.
+ * @return {@code true} if the listener was successfully registered, {@code false} otherwise.
+ *
+ * @hide
+ */
+ public boolean addGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) {
+ return mGpsMeasurementListenerTransport.add(listener);
+ }
+
+ /**
+ * Removes a GPS Measurement listener.
+ *
+ * @param listener a {@link GpsMeasurementsEvent.Listener} object to remove.
+ *
+ * @hide
+ */
+ public void removeGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) {
+ mGpsMeasurementListenerTransport.remove(listener);
+ }
+
/**
* Retrieves information about the current status of the GPS engine.
* This should only be called from the {@link GpsStatus.Listener#onGpsStatusChanged}
diff --git a/location/lib/java/com/android/location/provider/ActivityChangedEvent.java b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
new file mode 100644
index 0000000..8707a10
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityChangedEvent.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import android.annotation.NonNull;
+
+import java.security.InvalidParameterException;
+import java.util.List;
+
+/**
+ * A class representing an event for Activity changes.
+ */
+public class ActivityChangedEvent {
+ private final List<ActivityRecognitionEvent> mActivityRecognitionEvents;
+
+ public ActivityChangedEvent(List<ActivityRecognitionEvent> activityRecognitionEvents) {
+ if (activityRecognitionEvents == null) {
+ throw new InvalidParameterException(
+ "Parameter 'activityRecognitionEvents' must not be null.");
+ }
+
+ mActivityRecognitionEvents = activityRecognitionEvents;
+ }
+
+ @NonNull
+ public Iterable<ActivityRecognitionEvent> getActivityRecognitionEvents() {
+ return mActivityRecognitionEvents;
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
new file mode 100644
index 0000000..8c719ce4
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionEvent.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.location.provider;
+
+/**
+ * A class that represents an Activity Recognition Event.
+ */
+public class ActivityRecognitionEvent {
+ private final String mActivity;
+ private final int mEventType;
+ private final long mTimestampNs;
+
+ public ActivityRecognitionEvent(String activity, int eventType, long timestampNs) {
+ mActivity = activity;
+ mEventType = eventType;
+ mTimestampNs = timestampNs;
+ }
+
+ public String getActivity() {
+ return mActivity;
+ }
+
+ public int getEventType() {
+ return mEventType;
+ }
+
+ public long getTimestampNs() {
+ return mTimestampNs;
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
new file mode 100644
index 0000000..da33464
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProvider.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import com.android.internal.util.Preconditions;
+
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareSink;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * A class that exposes {@link IActivityRecognitionHardware} functionality to unbundled services.
+ */
+public final class ActivityRecognitionProvider {
+ private final IActivityRecognitionHardware mService;
+ private final HashSet<Sink> mSinkSet = new HashSet<Sink>();
+ private final SinkTransport mSinkTransport = new SinkTransport();
+
+ // the following constants must remain in sync with activity_recognition.h
+
+ public static final String ACTIVITY_IN_VEHICLE = "android.activity_recognition.in_vehicle";
+ public static final String ACTIVITY_ON_BICYCLE = "android.activity_recognition.on_bicycle";
+ public static final String ACTIVITY_WALKING = "android.activity_recognition.walking";
+ public static final String ACTIVITY_RUNNING = "android.activity_recognition.running";
+ public static final String ACTIVITY_STILL = "android.activity_recognition.still";
+ public static final String ACTIVITY_TILTING = "android.activity_recognition.tilting";
+
+ public static final int EVENT_TYPE_FLUSH_COMPLETE = 0;
+ public static final int EVENT_TYPE_ENTER = 1;
+ public static final int EVENT_TYPE_EXIT = 2;
+
+ // end constants activity_recognition.h
+
+ /**
+ * Used to receive Activity-Recognition events.
+ */
+ public interface Sink {
+ void onActivityChanged(ActivityChangedEvent event);
+ }
+
+ public ActivityRecognitionProvider(IActivityRecognitionHardware service)
+ throws RemoteException {
+ Preconditions.checkNotNull(service);
+ mService = service;
+ mService.registerSink(mSinkTransport);
+ }
+
+ public String[] getSupportedActivities() throws RemoteException {
+ return mService.getSupportedActivities();
+ }
+
+ public boolean isActivitySupported(String activity) throws RemoteException {
+ return mService.isActivitySupported(activity);
+ }
+
+ public void registerSink(Sink sink) {
+ Preconditions.checkNotNull(sink);
+ synchronized (mSinkSet) {
+ mSinkSet.add(sink);
+ }
+ }
+
+ // TODO: if this functionality is exposed to 3rd party developers, handle unregistration (here
+ // and in the service) of all sinks while failing to disable all events
+ public void unregisterSink(Sink sink) {
+ Preconditions.checkNotNull(sink);
+ synchronized (mSinkSet) {
+ mSinkSet.remove(sink);
+ }
+ }
+
+ public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs)
+ throws RemoteException {
+ return mService.enableActivityEvent(activity, eventType, reportLatencyNs);
+ }
+
+ public boolean disableActivityEvent(String activity, int eventType) throws RemoteException {
+ return mService.disableActivityEvent(activity, eventType);
+ }
+
+ public boolean flush() throws RemoteException {
+ return mService.flush();
+ }
+
+ private final class SinkTransport extends IActivityRecognitionHardwareSink.Stub {
+ @Override
+ public void onActivityChanged(
+ android.hardware.location.ActivityChangedEvent activityChangedEvent) {
+ Collection<Sink> sinks;
+ synchronized (mSinkSet) {
+ if (mSinkSet.isEmpty()) {
+ return;
+ }
+
+ sinks = new ArrayList<Sink>(mSinkSet);
+ }
+
+ // translate the event from platform internal and GmsCore types
+ ArrayList<ActivityRecognitionEvent> gmsEvents =
+ new ArrayList<ActivityRecognitionEvent>();
+ for (android.hardware.location.ActivityRecognitionEvent event
+ : activityChangedEvent.getActivityRecognitionEvents()) {
+ ActivityRecognitionEvent gmsEvent = new ActivityRecognitionEvent(
+ event.getActivity(),
+ event.getEventType(),
+ event.getTimestampNs());
+ gmsEvents.add(gmsEvent);
+ }
+ ActivityChangedEvent gmsEvent = new ActivityChangedEvent(gmsEvents);
+
+ for (Sink sink : sinks) {
+ sink.onActivityChanged(gmsEvent);
+ }
+ }
+ }
+}
diff --git a/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
new file mode 100644
index 0000000..03dd042
--- /dev/null
+++ b/location/lib/java/com/android/location/provider/ActivityRecognitionProviderWatcher.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.location.provider;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.hardware.location.IActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A watcher class for Activity-Recognition instances.
+ */
+public class ActivityRecognitionProviderWatcher {
+ private static final String TAG = "ActivityRecognitionProviderWatcher";
+
+ private static ActivityRecognitionProviderWatcher sWatcher;
+ private static final Object sWatcherLock = new Object();
+
+ private ActivityRecognitionProvider mActivityRecognitionProvider;
+
+ private ActivityRecognitionProviderWatcher() {}
+
+ public static ActivityRecognitionProviderWatcher getInstance() {
+ synchronized (sWatcherLock) {
+ if (sWatcher == null) {
+ sWatcher = new ActivityRecognitionProviderWatcher();
+ }
+ return sWatcher;
+ }
+ }
+
+ private IActivityRecognitionHardwareWatcher.Stub mWatcherStub =
+ new IActivityRecognitionHardwareWatcher.Stub() {
+ @Override
+ public void onInstanceChanged(IActivityRecognitionHardware instance) {
+ int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID) {
+ Log.d(TAG, "Ignoring calls from non-system server. Uid: " + callingUid);
+ return;
+ }
+
+ try {
+ mActivityRecognitionProvider = new ActivityRecognitionProvider(instance);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error creating Hardware Activity-Recognition", e);
+ }
+ }
+ };
+
+ /**
+ * Gets the binder needed to interact with proxy provider in the platform.
+ */
+ @NonNull
+ public IBinder getBinder() {
+ return mWatcherStub;
+ }
+
+ /**
+ * Gets an object that supports the functionality of {@link ActivityRecognitionProvider}.
+ *
+ * @return Non-null value if the functionality is supported by the platform, false otherwise.
+ */
+ @Nullable
+ public ActivityRecognitionProvider getActivityRecognitionProvider() {
+ return mActivityRecognitionProvider;
+ }
+}
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index d553d10..7a30920 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -412,16 +412,6 @@
}
};
- /** @hide */
- @Override
- public String toString () {
- return new String("AudioAttributes:"
- + " usage=" + mUsage
- + " content=" + mContentType
- + " flags=0x" + Integer.toHexString(mFlags)
- + " tags=" + mTags);
- }
-
@Override
public int describeContents() {
return 0;
@@ -486,6 +476,53 @@
}
};
+ /** @hide */
+ @Override
+ public String toString () {
+ return new String("AudioAttributes:"
+ + " usage=" + mUsage
+ + " content=" + mContentType
+ + " flags=0x" + Integer.toHexString(mFlags).toUpperCase()
+ + " tags=" + mTags);
+ }
+
+ /** @hide */
+ public String usageToString() {
+ switch(mUsage) {
+ case USAGE_UNKNOWN:
+ return new String("USAGE_UNKNOWN");
+ case USAGE_MEDIA:
+ return new String("USAGE_MEDIA");
+ case USAGE_VOICE_COMMUNICATION:
+ return new String("USAGE_VOICE_COMMUNICATION");
+ case USAGE_VOICE_COMMUNICATION_SIGNALLING:
+ return new String("USAGE_VOICE_COMMUNICATION");
+ case USAGE_ALARM:
+ return new String("USAGE_ALARM");
+ case USAGE_NOTIFICATION:
+ return new String("USAGE_NOTIFICATION");
+ case USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
+ return new String("USAGE_NOTIFICATION");
+ case USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+ return new String("USAGE_NOTIFICATION");
+ case USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+ return new String("USAGE_NOTIFICATION_COMMUNICATION_INSTANT");
+ case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+ return new String("USAGE_NOTIFICATION_COMMUNICATION_DELAYED");
+ case USAGE_NOTIFICATION_EVENT:
+ return new String("USAGE_NOTIFICATION_EVENT");
+ case USAGE_ASSISTANCE_ACCESSIBILITY:
+ return new String("USAGE_ASSISTANCE_ACCESSIBILITY");
+ case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+ return new String("USAGE_ASSISTANCE_NAVIGATION_GUIDANCE");
+ case USAGE_ASSISTANCE_SONIFICATION:
+ return new String("USAGE_ASSISTANCE_SONIFICATION");
+ case USAGE_GAME:
+ return new String("USAGE_GAME");
+ default:
+ return new String("unknown usage " + mUsage);
+ }
+ }
/** @hide */
@IntDef({
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index a471e83..025d354 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -227,19 +227,23 @@
private int mChannelMask;
private int mPropertySetMask;
- int getEncoding() {
+ /** @hide */
+ public int getEncoding() {
return mEncoding;
}
- int getSampleRate() {
+ /** @hide */
+ public int getSampleRate() {
return mSampleRate;
}
- int getChannelMask() {
+ /** @hide */
+ public int getChannelMask() {
return mChannelMask;
}
- int getPropertySetMask() {
+ /** @hide */
+ public int getPropertySetMask() {
return mPropertySetMask;
}
@@ -248,7 +252,7 @@
* Builder class for {@link AudioFormat} objects.
*/
public static class Builder {
- private int mEncoding = ENCODING_DEFAULT;
+ private int mEncoding = ENCODING_PCM_16BIT;
private int mSampleRate = 0;
private int mChannelMask = CHANNEL_INVALID;
private int mPropertySetMask = AUDIO_FORMAT_HAS_PROPERTY_NONE;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index c088b82..eb6bf2c 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -25,6 +25,8 @@
import android.content.Context;
import android.content.Intent;
import android.media.RemoteController.OnClientUpdateListener;
+import android.media.audiopolicy.AudioPolicy;
+import android.media.audiopolicy.AudioPolicyConfig;
import android.media.session.MediaSessionLegacyHelper;
import android.os.Binder;
import android.os.Handler;
@@ -2449,6 +2451,52 @@
}
/**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ * Register the given {@link AudioPolicy}.
+ * This call is synchronous and blocks until the registration process successfully completed
+ * or failed to complete.
+ * @param policy the {@link AudioPolicy} to register.
+ * @return {@link #ERROR} if there was an error communicating with the registration service
+ * or if the user doesn't have the required
+ * {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission,
+ * {@link #SUCCESS} otherwise.
+ */
+ public int registerAudioPolicy(AudioPolicy policy) {
+ if (policy == null) {
+ throw new IllegalArgumentException("Illegal null AudioPolicy argument");
+ }
+ IAudioService service = getService();
+ try {
+ if (!service.registerAudioPolicy(policy.getConfig(), policy.token())) {
+ return ERROR;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in registerAudioPolicyAsync()", e);
+ return ERROR;
+ }
+ return SUCCESS;
+ }
+
+ /**
+ * @hide
+ * CANDIDATE FOR PUBLIC API
+ * @param policy the {@link AudioPolicy} to unregister.
+ */
+ public void unregisterAudioPolicyAsync(AudioPolicy policy) {
+ if (policy == null) {
+ throw new IllegalArgumentException("Illegal null AudioPolicy argument");
+ }
+ IAudioService service = getService();
+ try {
+ service.unregisterAudioPolicyAsync(policy.token());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
+ }
+ }
+
+
+ /**
* @hide
* Reload audio settings. This method is called by Settings backup
* agent when audio settings are restored and causes the AudioService
@@ -2912,6 +2960,7 @@
*/
/** @hide
+ * CANDIDATE FOR PUBLIC API
*/
public static final int SUCCESS = AudioSystem.SUCCESS;
/**
@@ -2919,6 +2968,7 @@
*/
public static final int ERROR = AudioSystem.ERROR;
/** @hide
+ * CANDIDATE FOR PUBLIC API
*/
public static final int ERROR_BAD_VALUE = AudioSystem.BAD_VALUE;
/** @hide
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index a58a20f3..b08d631 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -48,6 +48,7 @@
import android.hardware.usb.UsbManager;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
+import android.media.audiopolicy.AudioPolicyConfig;
import android.media.session.MediaSessionLegacyHelper;
import android.os.Binder;
import android.os.Build;
@@ -68,6 +69,7 @@
import android.telecomm.TelecommManager;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Slog;
import android.view.KeyEvent;
import android.view.Surface;
import android.view.WindowManager;
@@ -5041,4 +5043,65 @@
}
}
}
+
+ //==========================================================================================
+ // Audio policy management
+ //==========================================================================================
+ public boolean registerAudioPolicy(AudioPolicyConfig policyConfig, IBinder cb) {
+ //Log.v(TAG, "registerAudioPolicy for " + cb + " got policy:" + policyConfig);
+ boolean hasPermissionForPolicy =
+ (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING));
+ if (!hasPermissionForPolicy) {
+ Slog.w(TAG, "Can't register audio policy for pid " + Binder.getCallingPid() + " / uid "
+ + Binder.getCallingUid() + ", need MODIFY_AUDIO_ROUTING");
+ return false;
+ }
+ synchronized (mAudioPolicies) {
+ AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
+ try {
+ cb.linkToDeath(app, 0/*flags*/);
+ mAudioPolicies.put(cb, app);
+ } catch (RemoteException e) {
+ // audio policy owner has already died!
+ Slog.w(TAG, "Audio policy registration failed, could not link to " + cb +
+ " binder death", e);
+ return false;
+ }
+ }
+ // TODO implement registration with native audio policy (including permission check)
+ return true;
+ }
+ public void unregisterAudioPolicyAsync(IBinder cb) {
+ synchronized (mAudioPolicies) {
+ AudioPolicyProxy app = mAudioPolicies.remove(cb);
+ if (app == null) {
+ Slog.w(TAG, "Trying to unregister unknown audio policy for pid "
+ + Binder.getCallingPid() + " / uid " + Binder.getCallingUid());
+ } else {
+ cb.unlinkToDeath(app, 0/*flags*/);
+ }
+ }
+ // TODO implement registration with native audio policy
+ }
+
+ public class AudioPolicyProxy implements IBinder.DeathRecipient {
+ private static final String TAG = "AudioPolicyProxy";
+ AudioPolicyConfig mConfig;
+ IBinder mToken;
+ AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
+ mConfig = config;
+ mToken = token;
+ }
+
+ public void binderDied() {
+ synchronized (mAudioPolicies) {
+ Log.v(TAG, "audio policy " + mToken + " died");
+ mAudioPolicies.remove(mToken);
+ }
+ }
+ };
+
+ private HashMap<IBinder, AudioPolicyProxy> mAudioPolicies =
+ new HashMap<IBinder, AudioPolicyProxy>();
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 9bf7ace8..e112a65 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -28,6 +28,7 @@
import android.media.IRingtonePlayer;
import android.media.IVolumeController;
import android.media.Rating;
+import android.media.audiopolicy.AudioPolicyConfig;
import android.net.Uri;
import android.view.KeyEvent;
@@ -199,4 +200,7 @@
void disableSafeMediaVolume();
int setHdmiSystemAudioSupported(boolean on, int device, String name);
+
+ boolean registerAudioPolicy(in AudioPolicyConfig policyConfig, IBinder cb);
+ oneway void unregisterAudioPolicyAsync(in IBinder cb);
}
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index a346e17..2856edb 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -19,6 +19,8 @@
import java.nio.ByteBuffer;
import java.lang.AutoCloseable;
+import android.graphics.Rect;
+
/**
* <p>A single complete image buffer to use with a media source such as a
* {@link MediaCodec} or a
@@ -121,6 +123,34 @@
*/
public abstract long getTimestamp();
+ protected Rect mCropRect;
+
+ /**
+ * Get the crop rectangle associated with this frame.
+ * <p>
+ * The crop rectangle specifies the region of valid pixels in the image,
+ * using coordinates in the largest-resolution plane.
+ */
+ public Rect getCropRect() {
+ if (mCropRect == null) {
+ return new Rect(0, 0, getWidth(), getHeight());
+ } else {
+ return new Rect(mCropRect); // return a copy
+ }
+ }
+
+ /**
+ * Set the crop rectangle associated with this frame.
+ * <p>
+ * The crop rectangle specifies the region of valid pixels in the image,
+ * using coordinates in the largest-resolution plane.
+ */
+ public void setCropRect(Rect cropRect) {
+ cropRect = new Rect(cropRect); // make a copy
+ cropRect.intersect(0, 0, getWidth(), getHeight());
+ mCropRect = cropRect;
+ }
+
/**
* Get the array of pixel planes for this Image. The number of planes is
* determined by the format of the Image.
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index e9a5f3f..cb9776a 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -16,6 +16,7 @@
package android.media;
+import android.media.Image;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaCrypto;
@@ -40,11 +41,15 @@
* MediaCodec codec = MediaCodec.createDecoderByType(type);
* codec.configure(format, ...);
* codec.start();
+ *
+ * // if API level <= 20, get input and output buffer arrays here
* ByteBuffer[] inputBuffers = codec.getInputBuffers();
* ByteBuffer[] outputBuffers = codec.getOutputBuffers();
* for (;;) {
* int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs);
* if (inputBufferIndex >= 0) {
+ * // if API level >= 21, get input buffer here
+ * ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex);
* // fill inputBuffers[inputBufferIndex] with valid data
* ...
* codec.queueInputBuffer(inputBufferIndex, ...);
@@ -52,13 +57,17 @@
*
* int outputBufferIndex = codec.dequeueOutputBuffer(timeoutUs);
* if (outputBufferIndex >= 0) {
+ * // if API level >= 21, get output buffer here
+ * ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex);
* // outputBuffer is ready to be processed or rendered.
* ...
* codec.releaseOutputBuffer(outputBufferIndex, ...);
* } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ * // no needed to handle if API level >= 21 and using getOutputBuffer(int)
* outputBuffers = codec.getOutputBuffers();
* } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
* // Subsequent data will conform to new format.
+ * // can ignore if API level >= 21 and using getOutputFormat(outputBufferIndex)
* MediaFormat format = codec.getOutputFormat();
* ...
* }
@@ -70,9 +79,11 @@
*
* Each codec maintains a number of input and output buffers that are
* referred to by index in API calls.
+ * <p>
+ * For API levels 20 and below:
* The contents of these buffers are represented by the ByteBuffer[] arrays
* accessible through {@link #getInputBuffers} and {@link #getOutputBuffers}.
- *
+ * <p>
* After a successful call to {@link #start} the client "owns" neither
* input nor output buffers, subsequent calls to {@link #dequeueInputBuffer}
* and {@link #dequeueOutputBuffer} then transfer ownership from the codec
@@ -101,19 +112,19 @@
* first few buffers submitted to the codec object after starting it must
* be codec specific data marked as such using the flag {@link #BUFFER_FLAG_CODEC_CONFIG}
* in a call to {@link #queueInputBuffer}.
- *
+ * <p>
* Codec specific data included in the format passed to {@link #configure}
* (in ByteBuffer entries with keys "csd-0", "csd-1", ...) is automatically
* submitted to the codec, this data MUST NOT be submitted explicitly by the
* client.
- *
+ * <p>
* Once the client reaches the end of the input data it signals the end of
* the input stream by specifying a flag of {@link #BUFFER_FLAG_END_OF_STREAM} in the call to
* {@link #queueInputBuffer}. The codec will continue to return output buffers
* until it eventually signals the end of the output stream by specifying
* the same flag ({@link #BUFFER_FLAG_END_OF_STREAM}) on the BufferInfo returned in
* {@link #dequeueOutputBuffer}.
- *
+ * <p>
* In order to start decoding data that's not adjacent to previously submitted
* data (i.e. after a seek) it is necessary to {@link #flush} the decoder.
* Any input or output buffers the client may own at the point of the flush are
@@ -166,9 +177,19 @@
final public class MediaCodec {
/**
* Per buffer metadata includes an offset and size specifying
- * the range of valid data in the associated codec buffer.
+ * the range of valid data in the associated codec (output) buffer.
*/
public final static class BufferInfo {
+ /**
+ * Update the buffer metadata information.
+ *
+ * @param newOffset the start-offset of the data in the buffer.
+ * @param newSize the amount of data (in bytes) in the buffer.
+ * @param newTimeUs the presentation timestamp in microseconds.
+ * @param newFlags buffer flags associated with the buffer. This
+ * should be a combination of {@link #BUFFER_FLAG_KEY_FRAME} and
+ * {@link #BUFFER_FLAG_END_OF_STREAM}.
+ */
public void set(
int newOffset, int newSize, long newTimeUs, int newFlags) {
offset = newOffset;
@@ -177,9 +198,39 @@
flags = newFlags;
}
+ /**
+ * The start-offset of the data in the buffer.
+ */
public int offset;
+
+ /**
+ * The amount of data (in bytes) in the buffer. If this is {@code 0},
+ * the buffer has no data in it and can be discarded. The only
+ * use of a 0-size buffer is to carry the end-of-stream marker.
+ */
public int size;
+
+ /**
+ * The presentation timestamp in microseconds for the buffer.
+ * This is derived from the presentation timestamp passed in
+ * with the corresponding input buffer. This should be ignored for
+ * a 0-sized buffer.
+ */
public long presentationTimeUs;
+
+ /**
+ * Buffer flags associated with the buffer. A combination of
+ * {@link #BUFFER_FLAG_KEY_FRAME} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+ *
+ * <p>Encoded buffers that are key frames are marked with
+ * {@link #BUFFER_FLAG_KEY_FRAME}.
+ *
+ * <p>The last output buffer corresponding to the input buffer
+ * marked with {@link #BUFFER_FLAG_END_OF_STREAM} will also be marked
+ * with {@link #BUFFER_FLAG_END_OF_STREAM}. In some cases this could
+ * be an empty buffer, whose sole purpose is to carry the end-of-stream
+ * marker.
+ */
public int flags;
};
@@ -187,10 +238,18 @@
// in MediaCodec.h !
/**
- * This indicates that the buffer marked as such contains the data
- * for a sync frame.
+ * This indicates that the (encoded) buffer marked as such contains
+ * the data for a key frame.
+ *
+ * @deprecated Use {@link #BUFFER_FLAG_KEY_FRAME} instead.
*/
- public static final int BUFFER_FLAG_SYNC_FRAME = 1;
+ public static final int BUFFER_FLAG_SYNC_FRAME = 1;
+
+ /**
+ * This indicates that the (encoded) buffer marked as such contains
+ * the data for a key frame.
+ */
+ public static final int BUFFER_FLAG_KEY_FRAME = 1;
/**
* This indicated that the buffer marked as such contains codec
@@ -202,12 +261,18 @@
* This signals the end of stream, i.e. no buffers will be available
* after this, unless of course, {@link #flush} follows.
*/
- public static final int BUFFER_FLAG_END_OF_STREAM = 4;
+ public static final int BUFFER_FLAG_END_OF_STREAM = 4;
private EventHandler mEventHandler;
- private volatile NotificationCallback mNotificationCallback;
+ private Callback mCallback;
- static final int EVENT_NOTIFY = 1;
+ private static final int EVENT_CALLBACK = 1;
+ private static final int EVENT_SET_CALLBACK = 2;
+
+ private static final int CB_INPUT_AVAILABLE = 1;
+ private static final int CB_OUTPUT_AVAILABLE = 2;
+ private static final int CB_ERROR = 3;
+ private static final int CB_OUTPUT_FORMAT_CHANGE = 4;
private class EventHandler extends Handler {
private MediaCodec mCodec;
@@ -220,12 +285,66 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case EVENT_NOTIFY:
+ case EVENT_CALLBACK:
{
- NotificationCallback cb = mNotificationCallback;
- if (cb != null) {
- cb.onCodecNotify(mCodec);
+ handleCallback(msg);
+ break;
+ }
+ case EVENT_SET_CALLBACK:
+ {
+ mCallback = (MediaCodec.Callback) msg.obj;
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+
+ private void handleCallback(Message msg) {
+ if (mCallback == null) {
+ return;
+ }
+
+ switch (msg.arg1) {
+ case CB_INPUT_AVAILABLE:
+ {
+ int index = msg.arg2;
+ synchronized(mBufferLock) {
+ validateInputByteBuffer(mCachedInputBuffers, index);
}
+ mCallback.onInputBufferAvailable(mCodec, index);
+ break;
+ }
+
+ case CB_OUTPUT_AVAILABLE:
+ {
+ int index = msg.arg2;
+ BufferInfo info = (MediaCodec.BufferInfo) msg.obj;
+ synchronized(mBufferLock) {
+ validateOutputByteBuffer(mCachedOutputBuffers, index, info);
+ }
+ mCallback.onOutputBufferAvailable(
+ mCodec, index, info);
+ break;
+ }
+
+ case CB_ERROR:
+ {
+ mCallback.onError(mCodec, (MediaCodec.CodecException) msg.obj);
+ break;
+ }
+
+ case CB_OUTPUT_FORMAT_CHANGE:
+ {
+ mCallback.onOutputFormatChanged(mCodec,
+ new MediaFormat((Map<String, Object>) msg.obj));
+ break;
+ }
+
+ default:
+ {
break;
}
}
@@ -299,6 +418,7 @@
} else {
mEventHandler = null;
}
+ mBufferLock = new Object();
native_setup(name, nameIsType, encoder);
}
@@ -313,7 +433,12 @@
* component instance instead of relying on the garbage collector
* to do this for you at some point in the future.
*/
- public native final void release();
+ public final void release() {
+ freeAllTrackedBuffers(); // free buffers first
+ native_release();
+ }
+
+ private native final void native_release();
/**
* If this codec is to be used as an encoder, pass this flag.
@@ -360,6 +485,8 @@
native_configure(keys, values, surface, crypto, flags);
}
+ private native final void native_setCallback(Callback cb);
+
private native final void native_configure(
String[] keys, Object[] values,
Surface surface, MediaCrypto crypto, int flags);
@@ -385,7 +512,14 @@
* @throws MediaCodec.CodecException upon codec error. Note that some codec errors
* for start may be attributed to future method calls.
*/
- public native final void start();
+ public final void start() {
+ native_start();
+ synchronized(mBufferLock) {
+ cacheBuffers(true /* input */);
+ cacheBuffers(false /* input */);
+ }
+ }
+ private native final void native_start();
/**
* Finish the decode/encode session, note that the codec instance
@@ -396,9 +530,11 @@
*/
public final void stop() {
native_stop();
+ freeAllTrackedBuffers();
if (mEventHandler != null) {
- mEventHandler.removeMessages(EVENT_NOTIFY);
+ mEventHandler.removeMessages(EVENT_CALLBACK);
+ mEventHandler.removeMessages(EVENT_SET_CALLBACK);
}
}
@@ -411,7 +547,19 @@
* @throws IllegalStateException if not in the Executing state.
* @throws MediaCodec.CodecException upon codec error.
*/
- public native final void flush();
+ public final void flush() {
+ synchronized(mBufferLock) {
+ invalidateByteBuffers(mCachedInputBuffers);
+ invalidateByteBuffers(mCachedOutputBuffers);
+ invalidateByteBuffers(mDequeuedInputBuffers);
+ invalidateByteBuffers(mDequeuedOutputBuffers);
+ freeImages(mDequeuedInputImages);
+ freeImages(mDequeuedOutputImages);
+ }
+ native_flush();
+ }
+
+ private native final void native_flush();
/**
* Thrown when an internal codec error occurs.
@@ -497,8 +645,11 @@
/**
* After filling a range of the input buffer at the specified index
- * submit it to the component.
- *
+ * submit it to the component. Once an input buffer is queued to
+ * the codec, it MUST not be used until it is later retrieved by
+ * {#getInputBuffer} in response to a {#dequeueInputBuffer}
+ * response.
+ * <p>
* Many decoders require the actual compressed data stream to be
* preceded by "codec specific data", i.e. setup data used to initialize
* the codec such as PPS/SPS in the case of AVC video or code tables
@@ -506,9 +657,16 @@
* The class {@link android.media.MediaExtractor} provides codec
* specific data as part of
* the returned track format in entries named "csd-0", "csd-1" ...
- *
- * These buffers should be submitted using the flag {@link #BUFFER_FLAG_CODEC_CONFIG}.
- *
+ * <p>
+ * These buffers can be submitted directly after {@link #start} or
+ * {@link #flush} by specifying the flag {@link
+ * #BUFFER_FLAG_CODEC_CONFIG}. However, if you configure the
+ * codec with a {@link MediaFormat} containing these keys, they
+ * will be automatically submitted by MediaCodec directly after
+ * start. Therefore, the use of {@link
+ * #BUFFER_FLAG_CODEC_CONFIG} flag is discouraged and is
+ * recommended only for advanced users.
+ * <p>
* To indicate that this is the final piece of input data (or rather that
* no more input data follows unless the decoder is subsequently flushed)
* specify the flag {@link #BUFFER_FLAG_END_OF_STREAM}.
@@ -517,15 +675,31 @@
* in a call to {@link #dequeueInputBuffer}.
* @param offset The byte offset into the input buffer at which the data starts.
* @param size The number of bytes of valid input data.
- * @param presentationTimeUs The time at which this buffer should be rendered.
- * @param flags A bitmask of flags {@link #BUFFER_FLAG_SYNC_FRAME},
- * {@link #BUFFER_FLAG_CODEC_CONFIG} or {@link #BUFFER_FLAG_END_OF_STREAM}.
+ * @param presentationTimeUs The presentation timestamp in microseconds for this
+ * buffer. This is normally the media time at which this
+ * buffer should be presented (rendered).
+ * @param flags A bitmask of flags
+ * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+ * While not prohibited, most codecs do not use the
+ * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
* @throws IllegalStateException if not in the Executing state.
* @throws MediaCodec.CodecException upon codec error.
* @throws CryptoException if a crypto object has been specified in
* {@link #configure}
*/
- public native final void queueInputBuffer(
+ public final void queueInputBuffer(
+ int index,
+ int offset, int size, long presentationTimeUs, int flags)
+ throws CryptoException {
+ synchronized(mBufferLock) {
+ invalidateByteBuffer(mCachedInputBuffers, index);
+ updateDequeuedByteBuffer(mDequeuedInputBuffers, index, null);
+ }
+ native_queueInputBuffer(
+ index, offset, size, presentationTimeUs, flags);
+ }
+
+ private native final void native_queueInputBuffer(
int index,
int offset, int size, long presentationTimeUs, int flags)
throws CryptoException;
@@ -618,16 +792,34 @@
* @param offset The byte offset into the input buffer at which the data starts.
* @param info Metadata required to facilitate decryption, the object can be
* reused immediately after this call returns.
- * @param presentationTimeUs The time at which this buffer should be rendered.
- * @param flags A bitmask of flags {@link #BUFFER_FLAG_SYNC_FRAME},
- * {@link #BUFFER_FLAG_CODEC_CONFIG} or {@link #BUFFER_FLAG_END_OF_STREAM}.
+ * @param presentationTimeUs The presentation timestamp in microseconds for this
+ * buffer. This is normally the media time at which this
+ * buffer should be presented (rendered).
+ * @param flags A bitmask of flags
+ * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+ * While not prohibited, most codecs do not use the
+ * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
* @throws IllegalStateException if not in the Executing state.
* @throws MediaCodec.CodecException upon codec error.
* @throws CryptoException if an error occurs while attempting to decrypt the buffer.
* An error code associated with the exception helps identify the
* reason for the failure.
*/
- public native final void queueSecureInputBuffer(
+ public final void queueSecureInputBuffer(
+ int index,
+ int offset,
+ CryptoInfo info,
+ long presentationTimeUs,
+ int flags) throws CryptoException {
+ synchronized(mBufferLock) {
+ invalidateByteBuffer(mCachedInputBuffers, index);
+ updateDequeuedByteBuffer(mDequeuedInputBuffers, index, null);
+ }
+ native_queueSecureInputBuffer(
+ index, offset, info, presentationTimeUs, flags);
+ }
+
+ private native final void native_queueSecureInputBuffer(
int index,
int offset,
CryptoInfo info,
@@ -644,7 +836,17 @@
* @throws IllegalStateException if not in the Executing state.
* @throws MediaCodec.CodecException upon codec error.
*/
- public native final int dequeueInputBuffer(long timeoutUs);
+ public final int dequeueInputBuffer(long timeoutUs) {
+ int res = native_dequeueInputBuffer(timeoutUs);
+ if (res >= 0) {
+ synchronized(mBufferLock) {
+ validateInputByteBuffer(mCachedInputBuffers, res);
+ }
+ }
+ return res;
+ }
+
+ private native final int native_dequeueInputBuffer(long timeoutUs);
/**
* If a non-negative timeout had been specified in the call
@@ -654,7 +856,10 @@
/**
* The output format has changed, subsequent data will follow the new
- * format. {@link #getOutputFormat} returns the new format.
+ * format. {@link #getOutputFormat()} returns the new format. Note, that
+ * you can also use the new {@link #getOutputFormat(int)} method to
+ * get the format for a specific output buffer. This frees you from
+ * having to track output format changes.
*/
public static final int INFO_OUTPUT_FORMAT_CHANGED = -2;
@@ -662,6 +867,11 @@
* The output buffers have changed, the client must refer to the new
* set of output buffers returned by {@link #getOutputBuffers} from
* this point on.
+ *
+ * @deprecated This return value can be ignored as {@link
+ * #getOutputBuffers} has been deprecated. Client should
+ * request a current buffer using on of the get-buffer or
+ * get-image methods each time one has been dequeued.
*/
public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3;
@@ -674,13 +884,31 @@
* @throws IllegalStateException if not in the Executing state.
* @throws MediaCodec.CodecException upon codec error.
*/
- public native final int dequeueOutputBuffer(
+ public final int dequeueOutputBuffer(
+ BufferInfo info, long timeoutUs) {
+ int res = native_dequeueOutputBuffer(info, timeoutUs);
+ synchronized(mBufferLock) {
+ if (res == INFO_OUTPUT_BUFFERS_CHANGED) {
+ cacheBuffers(false /* input */);
+ } else if (res >= 0) {
+ validateOutputByteBuffer(mCachedOutputBuffers, res, info);
+ }
+ }
+ return res;
+ }
+
+ private native final int native_dequeueOutputBuffer(
BufferInfo info, long timeoutUs);
/**
* If you are done with a buffer, use this call to return the buffer to
* the codec. If you previously specified a surface when configuring this
* video decoder you can optionally render the buffer.
+ *
+ * Once an output buffer is released to the codec, it MUST not
+ * be used until it is later retrieved by {#getOutputBuffer} in
+ * response to a {#dequeueOutputBuffer} response
+ *
* @param index The index of a client-owned output buffer previously returned
* from a call to {@link #dequeueOutputBuffer}.
* @param render If a valid surface was specified when configuring the codec,
@@ -689,6 +917,10 @@
* @throws MediaCodec.CodecException upon codec error.
*/
public final void releaseOutputBuffer(int index, boolean render) {
+ synchronized(mBufferLock) {
+ invalidateByteBuffer(mCachedOutputBuffers, index);
+ updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, null);
+ }
releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */);
}
@@ -729,12 +961,22 @@
* </td></tr>
* </table>
*
+ * Once an output buffer is released to the codec, it MUST not
+ * be used until it is later retrieved by {#getOutputBuffer} in
+ * response to a {#dequeueOutputBuffer} response
+ *
* @param index The index of a client-owned output buffer previously returned
* from a call to {@link #dequeueOutputBuffer}.
* @param renderTimestampNs The timestamp to associate with this buffer when
* it is sent to the Surface.
+ * @throws IllegalStateException if not in the Executing state.
+ * @throws MediaCodec.CodecException upon codec error.
*/
public final void releaseOutputBuffer(int index, long renderTimestampNs) {
+ synchronized(mBufferLock) {
+ invalidateByteBuffer(mCachedOutputBuffers, index);
+ updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, null);
+ }
releaseOutputBuffer(
index, true /* render */, true /* updatePTS */, renderTimestampNs);
}
@@ -753,34 +995,341 @@
/**
* Call this after dequeueOutputBuffer signals a format change by returning
- * {@link #INFO_OUTPUT_FORMAT_CHANGED}
- * @throws IllegalStateException if not in the Executing state.
+ * {@link #INFO_OUTPUT_FORMAT_CHANGED}.
+ * You can also call this after {@link #configure} returns
+ * successfully to get the output format initially configured
+ * for the codec. Do this to determine what optional
+ * configuration parameters were supported by the codec.
+ *
+ * @throws IllegalStateException if not in the Executing or
+ * Configured state.
* @throws MediaCodec.CodecException upon codec error.
*/
public final MediaFormat getOutputFormat() {
- return new MediaFormat(getOutputFormatNative());
+ return new MediaFormat(getFormatNative(false /* input */));
}
- private native final Map<String, Object> getOutputFormatNative();
+ /**
+ * Call this after {@link #configure} returns successfully to
+ * get the input format accepted by the codec. Do this to
+ * determine what optional configuration parameters were
+ * supported by the codec.
+ *
+ * @throws IllegalStateException if not in the Executing or
+ * Configured state.
+ * @throws MediaCodec.CodecException upon codec error.
+ */
+ public final MediaFormat getInputFormat() {
+ return new MediaFormat(getFormatNative(true /* input */));
+ }
/**
- * Call this after start() returns.
+ * Returns the output format for a specific output buffer.
+ *
+ * @param index The index of a client-owned input buffer previously
+ * returned from a call to {@link #dequeueInputBuffer}.
+ *
+ * @return the format for the output buffer, or null if the index
+ * is not a dequeued output buffer.
+ */
+ public final MediaFormat getOutputFormat(int index) {
+ return new MediaFormat(getOutputFormatNative(index));
+ }
+
+ private native final Map<String, Object> getFormatNative(boolean input);
+
+ private native final Map<String, Object> getOutputFormatNative(int index);
+
+ private ByteBuffer[] mCachedInputBuffers;
+ private ByteBuffer[] mCachedOutputBuffers;
+ private ByteBuffer[] mDequeuedInputBuffers;
+ private ByteBuffer[] mDequeuedOutputBuffers;
+ private Image[] mDequeuedInputImages;
+ private Image[] mDequeuedOutputImages;
+ final private Object mBufferLock;
+
+ private final void invalidateByteBuffer(
+ ByteBuffer[] buffers, int index) {
+ if (index >= 0 && index < buffers.length) {
+ ByteBuffer buffer = buffers[index];
+ if (buffer != null) {
+ buffer.setAccessible(false);
+ }
+ }
+ }
+
+ private final void validateInputByteBuffer(
+ ByteBuffer[] buffers, int index) {
+ if (index >= 0 && index < buffers.length) {
+ ByteBuffer buffer = buffers[index];
+ if (buffer != null) {
+ buffer.setAccessible(true);
+ buffer.clear();
+ }
+ }
+ }
+
+ private final void validateOutputByteBuffer(
+ ByteBuffer[] buffers, int index, BufferInfo info) {
+ if (index >= 0 && index < buffers.length) {
+ ByteBuffer buffer = buffers[index];
+ if (buffer != null) {
+ buffer.setAccessible(true);
+ buffer.limit(info.offset + info.size).position(info.offset);
+ }
+ }
+ }
+
+ private final void invalidateByteBuffers(ByteBuffer[] buffers) {
+ if (buffers != null) {
+ for (ByteBuffer buffer: buffers) {
+ if (buffer != null) {
+ buffer.setAccessible(false);
+ }
+ }
+ }
+ }
+
+ private final void freeByteBuffer(ByteBuffer buffer) {
+ if (buffer != null /* && buffer.isDirect() */) {
+ // all of our ByteBuffers are direct
+ java.nio.NioUtils.freeDirectBuffer(buffer);
+ }
+ }
+
+ private final void freeByteBuffers(ByteBuffer[] buffers) {
+ if (buffers != null) {
+ for (ByteBuffer buffer: buffers) {
+ freeByteBuffer(buffer);
+ }
+ }
+ }
+
+ private final void freeImage(Image image) {
+ if (image != null) {
+ image.close();
+ }
+ }
+
+ private final void freeImages(Image[] images) {
+ if (images != null) {
+ for (Image image: images) {
+ freeImage(image);
+ }
+ }
+ }
+
+ private final void freeAllTrackedBuffers() {
+ freeByteBuffers(mCachedInputBuffers);
+ freeByteBuffers(mCachedOutputBuffers);
+ freeImages(mDequeuedInputImages);
+ freeImages(mDequeuedOutputImages);
+ freeByteBuffers(mDequeuedInputBuffers);
+ freeByteBuffers(mDequeuedOutputBuffers);
+ mCachedInputBuffers = null;
+ mCachedOutputBuffers = null;
+ mDequeuedInputImages = null;
+ mDequeuedOutputImages = null;
+ mDequeuedInputBuffers = null;
+ mDequeuedOutputBuffers = null;
+ }
+
+ private final void cacheBuffers(boolean input) {
+ ByteBuffer[] buffers = getBuffers(input);
+ invalidateByteBuffers(buffers);
+ if (input) {
+ mCachedInputBuffers = buffers;
+ mDequeuedInputImages = new Image[buffers.length];
+ mDequeuedInputBuffers = new ByteBuffer[buffers.length];
+ } else {
+ mCachedOutputBuffers = buffers;
+ mDequeuedOutputImages = new Image[buffers.length];
+ mDequeuedOutputBuffers = new ByteBuffer[buffers.length];
+ }
+ }
+
+ /**
+ * Retrieve the set of input buffers. Call this after start()
+ * returns. After calling this method, any ByteBuffers
+ * previously returned by an earlier call to this method MUST no
+ * longer be used.
+ *
+ * @deprecated Use the new {@link #getInputBuffer} method instead
+ * each time an input buffer is dequeued.
+ *
* @throws IllegalStateException if not in the Executing state.
* @throws MediaCodec.CodecException upon codec error.
*/
public ByteBuffer[] getInputBuffers() {
- return getBuffers(true /* input */);
+ if (mCachedInputBuffers == null) {
+ throw new IllegalStateException();
+ }
+ // FIXME: check codec status
+ return mCachedInputBuffers;
}
/**
- * Call this after start() returns and whenever dequeueOutputBuffer
- * signals an output buffer change by returning
- * {@link #INFO_OUTPUT_BUFFERS_CHANGED}
- * @throws IllegalStateException if not in the Executing state.
+ * Retrieve the set of output buffers. Call this after start()
+ * returns and whenever dequeueOutputBuffer signals an output
+ * buffer change by returning {@link
+ * #INFO_OUTPUT_BUFFERS_CHANGED}. After calling this method, any
+ * ByteBuffers previously returned by an earlier call to this
+ * method MUST no longer be used.
+ *
+ * @deprecated Use the new {@link #getOutputBuffer} method instead
+ * each time an output buffer is dequeued. This method is not
+ * supported if codec is configured in asynchronous mode.
+ *
+ * @throws IllegalStateException if not in the Executing state,
+ * or codec is configured in asynchronous mode.
* @throws MediaCodec.CodecException upon codec error.
*/
public ByteBuffer[] getOutputBuffers() {
- return getBuffers(false /* input */);
+ if (mCachedOutputBuffers == null) {
+ throw new IllegalStateException();
+ }
+ // FIXME: check codec status
+ return mCachedOutputBuffers;
+ }
+
+ private boolean updateDequeuedByteBuffer(
+ ByteBuffer[] buffers, int index, ByteBuffer newBuffer) {
+ if (index < 0 || index >= buffers.length) {
+ return false;
+ }
+ freeByteBuffer(buffers[index]);
+ buffers[index] = newBuffer;
+ return newBuffer != null;
+ }
+
+ private boolean updateDequeuedImage(
+ Image[] images, int index, Image newImage) {
+ if (index < 0 || index >= images.length) {
+ return false;
+ }
+ freeImage(images[index]);
+ images[index] = newImage;
+ return newImage != null;
+ }
+
+ /**
+ * Returns a cleared, writable ByteBuffer object for a dequeued
+ * input buffer index to contain the input data.
+ *
+ * After calling this method any ByteBuffer or Image object
+ * previously returned for the same input index MUST no longer
+ * be used.
+ *
+ * @param index The index of a client-owned input buffer previously
+ * returned from a call to {@link #dequeueInputBuffer},
+ * or received via an onInputBufferAvailable callback.
+ *
+ * @return the input buffer, or null if the index is not a dequeued
+ * input buffer, or if the codec is configured for surface input.
+ *
+ * @throws IllegalStateException if not in the Executing state.
+ * @throws MediaCodec.CodecException upon codec error.
+ */
+ public ByteBuffer getInputBuffer(int index) {
+ ByteBuffer newBuffer = getBuffer(true /* input */, index);
+ synchronized(mBufferLock) {
+ if (updateDequeuedByteBuffer(mDequeuedInputBuffers, index, newBuffer)) {
+ updateDequeuedImage(mDequeuedInputImages, index, null);
+ invalidateByteBuffer(mCachedInputBuffers, index);
+ return newBuffer;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a writable Image object for a dequeued input buffer
+ * index to contain the raw input video frame.
+ *
+ * After calling this method any ByteBuffer or Image object
+ * previously returned for the same input index MUST no longer
+ * be used.
+ *
+ * @param index The index of a client-owned input buffer previously
+ * returned from a call to {@link #dequeueInputBuffer},
+ * or received via an onInputBufferAvailable callback.
+ *
+ * @return the input image, or null if the index is not a
+ * dequeued input buffer, or not a ByteBuffer that contains a
+ * raw image.
+ *
+ * @throws IllegalStateException if not in the Executing state.
+ * @throws MediaCodec.CodecException upon codec error.
+ */
+ public Image getInputImage(int index) {
+ Image newImage = getImage(true /* input */, index);
+ if (updateDequeuedImage(mDequeuedInputImages, index, newImage)) {
+ updateDequeuedByteBuffer(mDequeuedInputBuffers, index, null);
+ invalidateByteBuffer(mCachedInputBuffers, index);
+ return newImage;
+ }
+ return null;
+ }
+
+ /**
+ * Returns a read-only ByteBuffer for a dequeued output buffer
+ * index. The position and limit of the returned buffer are set
+ * to the valid output data.
+ *
+ * After calling this method, any ByteBuffer or Image object
+ * previously returned for the same output index MUST no longer
+ * be used.
+ *
+ * @param index The index of a client-owned output buffer previously
+ * returned from a call to {@link #dequeueOutputBuffer},
+ * or received via an onOutputBufferAvailable callback.
+ *
+ * @return the output buffer, or null if the index is not a dequeued
+ * output buffer, or the codec is configured with an output surface.
+ *
+ * @throws IllegalStateException if not in the Executing state.
+ * @throws MediaCodec.CodecException upon codec error.
+ */
+ public ByteBuffer getOutputBuffer(int index) {
+ ByteBuffer newBuffer = getBuffer(false /* input */, index);
+ synchronized(mBufferLock) {
+ if (updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, newBuffer)) {
+ updateDequeuedImage(mDequeuedOutputImages, index, null);
+ invalidateByteBuffer(mCachedOutputBuffers, index);
+ return newBuffer;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns a read-only Image object for a dequeued output buffer
+ * index that contains the raw video frame.
+ *
+ * After calling this method, any ByteBuffer or Image object previously
+ * returned for the same output index MUST no longer be used.
+ *
+ * @param index The index of a client-owned output buffer previously
+ * returned from a call to {@link #dequeueOutputBuffer},
+ * or received via an onOutputBufferAvailable callback.
+ *
+ * @return the output image, or null if the index is not a
+ * dequeued output buffer, not a raw video frame, or if the codec
+ * was configured with an output surface.
+ *
+ * @throws IllegalStateException if not in the Executing state.
+ * @throws MediaCodec.CodecException upon codec error.
+ */
+ public Image getOutputImage(int index) {
+ Image newImage = getImage(false /* input */, index);
+ synchronized(mBufferLock) {
+ if (updateDequeuedImage(mDequeuedOutputImages, index, newImage)) {
+ updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, null);
+ invalidateByteBuffer(mCachedOutputBuffers, index);
+ return newImage;
+ }
+ }
+ return null;
}
/**
@@ -855,44 +1404,71 @@
}
/**
- * Sets the codec listener for actionable MediaCodec events.
- * <p>Call this method with a null listener to stop receiving event notifications.
+ * Sets an asynchronous callback for actionable MediaCodec events.
*
- * @param cb The listener that will run.
+ * If the client intends to use the component in asynchronous mode,
+ * a valid callback should be provided before {@link #configure} is called.
*
- * @hide
+ * When asynchronous callback is enabled, the client should not call
+ * {@link #dequeueInputBuffer(long)} or {@link #dequeueOutputBuffer(BufferInfo, long)}
+ *
+ * @param cb The callback that will run.
*/
- public void setNotificationCallback(NotificationCallback cb) {
- mNotificationCallback = cb;
+ public void setCallback(/* MediaCodec. */ Callback cb) {
+ if (mEventHandler != null) {
+ // set java callback on handler
+ Message msg = mEventHandler.obtainMessage(EVENT_SET_CALLBACK, 0, 0, cb);
+ mEventHandler.sendMessage(msg);
+
+ // set native handler here, don't post to handler because
+ // it may cause the callback to be delayed and set in a wrong state,
+ // and MediaCodec is already doing it on looper.
+ native_setCallback(cb);
+ }
}
/**
- * MediaCodec listener interface. Used to notify the user of MediaCodec
- * when there are available input and/or output buffers, a change in
- * configuration or when a codec error happened.
- *
- * @hide
+ * MediaCodec callback interface. Used to notify the user asynchronously
+ * of various MediaCodec events.
*/
- public static abstract class NotificationCallback {
+ public static abstract class Callback {
/**
- * Called on the listener to notify that there is an actionable
- * MediaCodec event. The application should call {@link #dequeueOutputBuffer}
- * to receive the configuration change event, codec error or an
- * available output buffer. It should also call {@link #dequeueInputBuffer}
- * to receive any available input buffer. For best performance, it
- * is recommended to exhaust both available input and output buffers in
- * the handling of a single callback, by calling the dequeue methods
- * repeatedly with a zero timeout until {@link #INFO_TRY_AGAIN_LATER} is returned.
+ * Called when an input buffer becomes available.
*
- * @param codec the MediaCodec instance that has an actionable event.
- *
+ * @param codec The MediaCodec object.
+ * @param index The index of the available input buffer.
*/
- public abstract void onCodecNotify(MediaCodec codec);
+ public abstract void onInputBufferAvailable(MediaCodec codec, int index);
+
+ /**
+ * Called when an output buffer becomes available.
+ *
+ * @param codec The MediaCodec object.
+ * @param index The index of the available output buffer.
+ * @param info Info regarding the available output buffer {@link MediaCodec.BufferInfo}.
+ */
+ public abstract void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info);
+
+ /**
+ * Called when the MediaCodec encountered an error
+ *
+ * @param codec The MediaCodec object.
+ * @param e The {@link MediaCodec.CodecException} object describing the error.
+ */
+ public abstract void onError(MediaCodec codec, CodecException e);
+
+ /**
+ * Called when the output format has changed
+ *
+ * @param codec The MediaCodec object.
+ * @param format The new output format.
+ */
+ public abstract void onOutputFormatChanged(MediaCodec codec, MediaFormat format);
}
private void postEventFromNative(
int what, int arg1, int arg2, Object obj) {
- if (mEventHandler != null && mNotificationCallback != null) {
+ if (mEventHandler != null) {
Message msg = mEventHandler.obtainMessage(what, arg1, arg2, obj);
mEventHandler.sendMessage(msg);
}
@@ -913,6 +1489,10 @@
private native final ByteBuffer[] getBuffers(boolean input);
+ private native final ByteBuffer getBuffer(boolean input, int index);
+
+ private native final Image getImage(boolean input, int index);
+
private static native final void native_init();
private native final void native_setup(
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 490a8fd..ab65ba0 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -631,6 +631,7 @@
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
@@ -1664,7 +1665,6 @@
public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
public static final int MEDIA_TRACK_TYPE_AUDIO = 2;
public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3;
- /** @hide */
public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
final int mTrackType;
@@ -2085,6 +2085,7 @@
throws IllegalArgumentException, IllegalStateException {
if (!availableMimeTypeForExternalSource(mimeType)) {
throw new IllegalArgumentException("Illegal mimeType for timed text source: " + mimeType);
+
}
Parcel request = Parcel.obtain();
@@ -2104,6 +2105,49 @@
}
/**
+ * 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 #release()}
+ *
+ * @see {@link #getTrackInfo()}
+ * @see {@link #selectTrack(int)}
+ * @see {@link #deselectTrack(int)}
+ */
+ public int getSelectedTrack(int trackType) throws IllegalStateException {
+ if (trackType == TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE && mSubtitleController != null) {
+ SubtitleTrack subtitleTrack = mSubtitleController.getSelectedTrack();
+ if (subtitleTrack != null) {
+ int index = mOutOfBandSubtitleTracks.indexOf(subtitleTrack);
+ if (index >= 0) {
+ return mInbandSubtitleTracks.length + index;
+ }
+ }
+ }
+
+ Parcel request = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ request.writeInterfaceToken(IMEDIA_PLAYER);
+ request.writeInt(INVOKE_ID_GET_SELECTED_TRACK);
+ request.writeInt(trackType);
+ invoke(request, reply);
+ int selectedTrack = reply.readInt();
+ return selectedTrack;
+ } finally {
+ request.recycle();
+ reply.recycle();
+ }
+ }
+
+ /**
* Selects a track.
* <p>
* If a MediaPlayer is in invalid state, it throws an IllegalStateException exception.
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 21e2f4b..f8c0494 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -25,8 +25,8 @@
import android.view.Surface;
import java.io.FileDescriptor;
-import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
/**
@@ -713,11 +713,11 @@
public void prepare() throws IllegalStateException, IOException
{
if (mPath != null) {
- FileOutputStream fos = new FileOutputStream(mPath);
+ RandomAccessFile file = new RandomAccessFile(mPath, "rws");
try {
- _setOutputFile(fos.getFD(), 0, 0);
+ _setOutputFile(file.getFD(), 0, 0);
} finally {
- fos.close();
+ file.close();
}
} else if (mFd != null) {
_setOutputFile(mFd, 0, 0);
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
new file mode 100644
index 0000000..f7967f1
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audiopolicy;
+
+import android.annotation.IntDef;
+import android.media.AudioFormat;
+import android.media.AudioSystem;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide CANDIDATE FOR PUBLIC API
+ */
+public class AudioMix {
+
+ private AudioMixingRule mRule;
+ private AudioFormat mFormat;
+ private int mRouteFlags;
+
+ /**
+ * All parameters are guaranteed valid through the Builder.
+ */
+ private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags) {
+ mRule = rule;
+ mFormat = format;
+ mRouteFlags = routeFlags;
+ }
+
+ /**
+ * An audio mix behavior where the output of the mix is sent to the original destination of
+ * the audio signal, i.e. an output device for an output mix, or a recording for an input mix.
+ */
+ public static final int ROUTE_FLAG_RENDER = 0x1;
+ /**
+ * An audio mix behavior where the output of the mix is rerouted back to the framework and
+ * is accessible for injection or capture through the {@link Audiotrack} and {@link AudioRecord}
+ * APIs.
+ */
+ public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
+
+ int getRouteFlags() {
+ return mRouteFlags;
+ }
+
+ AudioFormat getFormat() {
+ return mFormat;
+ }
+
+ AudioMixingRule getRule() {
+ return mRule;
+ }
+
+ /** @hide */
+ @IntDef(flag = true,
+ value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RouteFlags {}
+
+ /**
+ * Builder class for {@link AudioMix} objects
+ *
+ */
+ public static class Builder {
+ private AudioMixingRule mRule = null;
+ private AudioFormat mFormat = null;
+ private int mRouteFlags = 0;
+
+ /**
+ * @hide
+ * Only used by AudioPolicyConfig, not a public API.
+ */
+ Builder() { }
+
+ /**
+ * Construct an instance for the given {@link AudioMixingRule}.
+ * @param rule a non-null {@link AudioMixingRule} instance.
+ * @throws IllegalArgumentException
+ */
+ public Builder(AudioMixingRule rule)
+ throws IllegalArgumentException {
+ if (rule == null) {
+ throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
+ }
+ mRule = rule;
+ }
+
+ /**
+ * @hide
+ * Only used by AudioPolicyConfig, not a public API.
+ * @param rule
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException
+ */
+ public Builder setMixingRule(AudioMixingRule rule)
+ throws IllegalArgumentException {
+ if (rule == null) {
+ throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
+ }
+ mRule = rule;
+ return this;
+ }
+
+ /**
+ * Sets the {@link AudioFormat} for the mix.
+ * @param format a non-null {@link AudioFormat} instance.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException
+ */
+ public Builder setFormat(AudioFormat format)
+ throws IllegalArgumentException {
+ if (format == null) {
+ throw new IllegalArgumentException("Illegal null AudioFormat argument");
+ }
+ mFormat = format;
+ return this;
+ }
+
+ /**
+ * Sets the routing behavior for the mix.
+ * @param routeFlags one of {@link AudioMix#ROUTE_FLAG_LOOP_BACK},
+ * {@link AudioMix#ROUTE_FLAG_RENDER}
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException
+ */
+ public Builder setRouteFlags(@RouteFlags int routeFlags)
+ throws IllegalArgumentException {
+ if (routeFlags == 0) {
+ throw new IllegalArgumentException("Illegal empty route flags");
+ }
+ if ((routeFlags & (ROUTE_FLAG_LOOP_BACK | ROUTE_FLAG_RENDER)) == 0) {
+ throw new IllegalArgumentException("Invalid route flags 0x"
+ + Integer.toHexString(routeFlags) + "when creating an AudioMix");
+ }
+ mRouteFlags = routeFlags;
+ return this;
+ }
+
+ /**
+ * Combines all of the settings and return a new {@link AudioMix} object.
+ * @return a new {@link AudioMix} object
+ * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
+ */
+ public AudioMix build() throws IllegalArgumentException {
+ if (mRule == null) {
+ throw new IllegalArgumentException("Illegal null AudioMixingRule");
+ }
+ if (mRouteFlags == 0) {
+ // no route flags set, use default
+ mRouteFlags = ROUTE_FLAG_RENDER;
+ }
+ if (mFormat == null) {
+ int rate = AudioSystem.getPrimaryOutputSamplingRate();
+ if (rate <= 0) {
+ rate = 44100;
+ }
+ mFormat = new AudioFormat.Builder().setSampleRate(rate).build();
+ }
+ return new AudioMix(mRule, mFormat, mRouteFlags);
+ }
+ }
+}
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
new file mode 100644
index 0000000..ced7881
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audiopolicy;
+
+import android.media.AudioAttributes;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+
+/**
+ * @hide CANDIDATE FOR PUBLIC API
+ *
+ * Here's an example of creating a mixing rule for all media playback:
+ * <pre>
+ * AudioAttributes mediaAttr = new AudioAttributes.Builder()
+ * .setUsage(AudioAttributes.USAGE_MEDIA)
+ * .build();
+ * AudioMixingRule mediaRule = new AudioMixingRule.Builder()
+ * .addRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
+ * .build();
+ * </pre>
+ */
+public class AudioMixingRule {
+
+ private AudioMixingRule(ArrayList<AttributeMatchCriterion> criteria) {
+ mCriteria = criteria;
+ }
+
+ /**
+ * A rule requiring the usage information of the {@link AudioAttributes} to match
+ */
+ public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
+ /**
+ * A rule requiring the usage information of the {@link AudioAttributes} to differ
+ */
+ public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = 0x1 << 1;
+
+ static final class AttributeMatchCriterion {
+ AudioAttributes mAttr;
+ int mRule;
+
+ AttributeMatchCriterion(AudioAttributes attributes, int rule) {
+ mAttr = attributes;
+ mRule = rule;
+ }
+ }
+
+ private ArrayList<AttributeMatchCriterion> mCriteria;
+ ArrayList<AttributeMatchCriterion> getCriteria() { return mCriteria; }
+
+ /**
+ * Builder class for {@link AudioMixingRule} objects
+ *
+ */
+ public static class Builder {
+ private ArrayList<AttributeMatchCriterion> mCriteria;
+
+ /**
+ * Constructs a new Builder with no rules.
+ */
+ public Builder() {
+ mCriteria = new ArrayList<AttributeMatchCriterion>();
+ }
+
+ /**
+ * Add a rule for the selection of which streams are mixed together.
+ * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
+ * rule hasn't been set yet.
+ * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
+ * {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException
+ */
+ public Builder addRule(AudioAttributes attrToMatch, int rule)
+ throws IllegalArgumentException {
+ if (attrToMatch == null) {
+ throw new IllegalArgumentException("Illegal null AudioAttributes argument");
+ }
+ if ((rule != RULE_MATCH_ATTRIBUTE_USAGE) && (rule != RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+ throw new IllegalArgumentException("Illegal rule value " + rule);
+ }
+ synchronized (mCriteria) {
+ Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator();
+ while (crIterator.hasNext()) {
+ final AttributeMatchCriterion criterion = crIterator.next();
+ if ((rule == RULE_MATCH_ATTRIBUTE_USAGE)
+ || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+ // "usage"-based rule
+ if (criterion.mAttr.getUsage() == attrToMatch.getUsage()) {
+ if (criterion.mRule == rule) {
+ // rule already exists, we're done
+ return this;
+ } else {
+ // criterion already exists with a another rule, it is incompatible
+ throw new IllegalArgumentException("Contradictory rule exists for "
+ + attrToMatch);
+ }
+ }
+ }
+ }
+ // rule didn't exist, add it
+ mCriteria.add(new AttributeMatchCriterion(attrToMatch, rule));
+ }
+ return this;
+ }
+
+ /**
+ * Combines all of the matching and exclusion rules that have been set and return a new
+ * {@link AudioMixingRule} object.
+ * @return a new {@link AudioMixingRule} object
+ */
+ public AudioMixingRule build() {
+ return new AudioMixingRule(mCriteria);
+ }
+ }
+}
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
new file mode 100644
index 0000000..314eb88
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audiopolicy;
+
+import android.annotation.IntDef;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+
+/**
+ * @hide CANDIDATE FOR PUBLIC API
+ * AudioPolicy provides access to the management of audio routing and audio focus.
+ */
+public class AudioPolicy {
+
+ private static final String TAG = "AudioPolicy";
+
+ /**
+ * The status of an audio policy that cannot be used because it is invalid.
+ */
+ public static final int POLICY_STATUS_INVALID = 0;
+ /**
+ * The status of an audio policy that is valid but cannot be used because it is not registered.
+ */
+ public static final int POLICY_STATUS_UNREGISTERED = 1;
+ /**
+ * The status of an audio policy that is valid, successfully registered and thus active.
+ */
+ public static final int POLICY_STATUS_REGISTERED = 2;
+
+ private int mStatus;
+ private AudioPolicyStatusListener mStatusListener = null;
+
+ private final IBinder mToken = new Binder();
+ /** @hide */
+ public IBinder token() { return mToken; }
+
+ private AudioPolicyConfig mConfig;
+ /** @hide */
+ public AudioPolicyConfig getConfig() { return mConfig; }
+
+ /**
+ * The parameter is guaranteed non-null through the Builder
+ */
+ private AudioPolicy(AudioPolicyConfig config) {
+ mConfig = config;
+ if (mConfig.mMixes.isEmpty()) {
+ mStatus = POLICY_STATUS_INVALID;
+ } else {
+ mStatus = POLICY_STATUS_UNREGISTERED;
+ }
+ }
+
+ /**
+ * Builder class for {@link AudioPolicy} objects
+ */
+ public static class Builder {
+ private ArrayList<AudioMix> mMixes;
+
+ /**
+ * Constructs a new Builder with no audio mixes.
+ */
+ public Builder() {
+ mMixes = new ArrayList<AudioMix>();
+ }
+
+ /**
+ * Add an {@link AudioMix} to be part of the audio policy being built.
+ * @param mix a non-null {@link AudioMix} to be part of the audio policy.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException
+ */
+ public Builder addMix(AudioMix mix) throws IllegalArgumentException {
+ if (mix == null) {
+ throw new IllegalArgumentException("Illegal null AudioMix argument");
+ }
+ mMixes.add(mix);
+ return this;
+ }
+
+ public AudioPolicy build() {
+ return new AudioPolicy(new AudioPolicyConfig(mMixes));
+ }
+ }
+
+
+ public int getStatus() {
+ return mStatus;
+ }
+
+ public static abstract class AudioPolicyStatusListener {
+ void onStatusChange() {}
+ void onMixStateUpdate(AudioMix mix) {}
+ }
+
+ void setStatusListener(AudioPolicyStatusListener l) {
+ mStatusListener = l;
+ }
+
+ /** @hide */
+ @Override
+ public String toString () {
+ String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
+ textDump += "config=" + mConfig.toString();
+ return (textDump);
+ }
+
+ /** @hide */
+ @IntDef({
+ POLICY_STATUS_INVALID,
+ POLICY_STATUS_REGISTERED,
+ POLICY_STATUS_UNREGISTERED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PolicyStatus {}
+}
diff --git a/core/java/android/bluetooth/le/AdvertisementData.aidl b/media/java/android/media/audiopolicy/AudioPolicyConfig.aidl
similarity index 89%
copy from core/java/android/bluetooth/le/AdvertisementData.aidl
copy to media/java/android/media/audiopolicy/AudioPolicyConfig.aidl
index 3da1321..3ae1175 100644
--- a/core/java/android/bluetooth/le/AdvertisementData.aidl
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.aidl
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.bluetooth.le;
+package android.media.audiopolicy;
-parcelable AdvertisementData;
\ No newline at end of file
+parcelable AudioPolicyConfig;
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
new file mode 100644
index 0000000..2fc6d58
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audiopolicy;
+
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiopolicy.AudioMixingRule.AttributeMatchCriterion;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ * Internal storage class for AudioPolicy configuration.
+ */
+public class AudioPolicyConfig implements Parcelable {
+
+ private static final String TAG = "AudioPolicyConfig";
+
+ ArrayList<AudioMix> mMixes;
+
+ AudioPolicyConfig(ArrayList<AudioMix> mixes) {
+ mMixes = mixes;
+ }
+
+ /**
+ * Add an {@link AudioMix} to be part of the audio policy being built.
+ * @param mix a non-null {@link AudioMix} to be part of the audio policy.
+ * @return the same Builder instance.
+ * @throws IllegalArgumentException
+ */
+ public void addMix(AudioMix mix) throws IllegalArgumentException {
+ if (mix == null) {
+ throw new IllegalArgumentException("Illegal null AudioMix argument");
+ }
+ mMixes.add(mix);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mMixes.size());
+ for (AudioMix mix : mMixes) {
+ // write mix route flags
+ dest.writeInt(mix.getRouteFlags());
+ // write mix format
+ dest.writeInt(mix.getFormat().getSampleRate());
+ dest.writeInt(mix.getFormat().getEncoding());
+ dest.writeInt(mix.getFormat().getChannelMask());
+ // write mix rules
+ final ArrayList<AttributeMatchCriterion> criteria = mix.getRule().getCriteria();
+ dest.writeInt(criteria.size());
+ for (AttributeMatchCriterion criterion : criteria) {
+ dest.writeInt(criterion.mRule);
+ dest.writeInt(criterion.mAttr.getUsage());
+ }
+ }
+ }
+
+ private AudioPolicyConfig(Parcel in) {
+ mMixes = new ArrayList<AudioMix>();
+ int nbMixes = in.readInt();
+ for (int i = 0 ; i < nbMixes ; i++) {
+ final AudioMix.Builder mixBuilder = new AudioMix.Builder();
+ // read mix route flags
+ int routeFlags = in.readInt();
+ mixBuilder.setRouteFlags(routeFlags);
+ // read mix format
+ int sampleRate = in.readInt();
+ int encoding = in.readInt();
+ int channelMask = in.readInt();
+ final AudioFormat format = new AudioFormat.Builder().setSampleRate(sampleRate)
+ .setChannelMask(channelMask).setEncoding(encoding).build();
+ mixBuilder.setFormat(format);
+ // read mix rules
+ int nbRules = in.readInt();
+ AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
+ for (int j = 0 ; j < nbRules ; j++) {
+ // read the matching rules
+ int matchRule = in.readInt();
+ if ((matchRule == AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE)
+ || (matchRule == AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)) {
+ int usage = in.readInt();
+ final AudioAttributes attr = new AudioAttributes.Builder()
+ .setUsage(usage).build();
+ ruleBuilder.addRule(attr, matchRule);
+ } else {
+ Log.w(TAG, "Encountered unsupported rule, skipping");
+ in.readInt();
+ }
+ }
+ mixBuilder.setMixingRule(ruleBuilder.build());
+ mMixes.add(mixBuilder.build());
+ }
+ }
+
+ /** @hide */
+ public static final Parcelable.Creator<AudioPolicyConfig> CREATOR
+ = new Parcelable.Creator<AudioPolicyConfig>() {
+ /**
+ * Rebuilds an AudioPolicyConfig previously stored with writeToParcel().
+ * @param p Parcel object to read the AudioPolicyConfig from
+ * @return a new AudioPolicyConfig created from the data in the parcel
+ */
+ public AudioPolicyConfig createFromParcel(Parcel p) {
+ return new AudioPolicyConfig(p);
+ }
+ public AudioPolicyConfig[] newArray(int size) {
+ return new AudioPolicyConfig[size];
+ }
+ };
+
+ /** @hide */
+ @Override
+ public String toString () {
+ String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
+ textDump += mMixes.size() + " AudioMix:\n";
+ for(AudioMix mix : mMixes) {
+ // write mix route flags
+ textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n";
+ // write mix format
+ textDump += " rate=" + mix.getFormat().getSampleRate() + "Hz\n";
+ textDump += " encoding=" + mix.getFormat().getEncoding() + "\n";
+ textDump += " channels=0x";
+ textDump += Integer.toHexString(mix.getFormat().getChannelMask()).toUpperCase() +"\n";
+ // write mix rules
+ final ArrayList<AttributeMatchCriterion> criteria = mix.getRule().getCriteria();
+ for (AttributeMatchCriterion criterion : criteria) {
+ switch(criterion.mRule) {
+ case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE:
+ textDump += " exclude usage ";
+ textDump += criterion.mAttr.usageToString();
+ break;
+ case AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE:
+ textDump += " match usage ";
+ textDump += criterion.mAttr.usageToString();
+ break;
+ default:
+ textDump += "invalid rule!";
+ }
+ textDump += "\n";
+ }
+ }
+ return textDump;
+ }
+}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index bdebd7c..9a6a648 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -45,6 +45,7 @@
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
void setVolume(in IBinder sessionToken, float volume, int userId);
void tune(in IBinder sessionToken, in Uri channelUri, int userId);
+ void setCaptionEnabled(in IBinder sessionToken, boolean enabled, int userId);
void selectTrack(in IBinder sessionToken, in TvTrackInfo track, int userId);
void unselectTrack(in IBinder sessionToken, in TvTrackInfo track, int userId);
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index f8590f1..875fa34 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -33,6 +33,7 @@
// is to introduce some new concepts that will solve a number of problems in audio policy today.
void setVolume(float volume);
void tune(in Uri channelUri);
+ void setCaptionEnabled(boolean enabled);
void selectTrack(in TvTrackInfo track);
void unselectTrack(in TvTrackInfo track);
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 06ee4b5..0c08590 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -44,11 +44,12 @@
private static final int DO_SET_SURFACE = 2;
private static final int DO_SET_VOLUME = 3;
private static final int DO_TUNE = 4;
- private static final int DO_SELECT_TRACK = 5;
- private static final int DO_UNSELECT_TRACK = 6;
- private static final int DO_CREATE_OVERLAY_VIEW = 7;
- private static final int DO_RELAYOUT_OVERLAY_VIEW = 8;
- private static final int DO_REMOVE_OVERLAY_VIEW = 9;
+ private static final int DO_SET_CAPTION_ENABLED = 5;
+ private static final int DO_SELECT_TRACK = 6;
+ private static final int DO_UNSELECT_TRACK = 7;
+ private static final int DO_CREATE_OVERLAY_VIEW = 8;
+ private static final int DO_RELAYOUT_OVERLAY_VIEW = 9;
+ private static final int DO_REMOVE_OVERLAY_VIEW = 10;
private final HandlerCaller mCaller;
@@ -98,6 +99,10 @@
mTvInputSessionImpl.tune((Uri) msg.obj);
return;
}
+ case DO_SET_CAPTION_ENABLED: {
+ mTvInputSessionImpl.setCaptionEnabled((Boolean) msg.obj);
+ return;
+ }
case DO_SELECT_TRACK: {
mTvInputSessionImpl.selectTrack((TvTrackInfo) msg.obj);
return;
@@ -148,6 +153,11 @@
}
@Override
+ public void setCaptionEnabled(boolean enabled) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_CAPTION_ENABLED, enabled));
+ }
+
+ @Override
public void selectTrack(TvTrackInfo track) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SELECT_TRACK, track));
}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 521c809..f1a63ad 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -35,7 +35,6 @@
import android.view.View;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -575,6 +574,23 @@
}
/**
+ * Enables or disables the caption for this session.
+ *
+ * @param enabled {@code true} to enable, {@code false} to disable.
+ */
+ public void setCaptionEnabled(boolean enabled) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.setCaptionEnabled(mToken, enabled, mUserId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* Select a track.
*
* @param track the track to be selected.
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 4614bda..ba12e2e 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -16,6 +16,7 @@
package android.media.tv;
+import android.annotation.SuppressLint;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -40,6 +41,7 @@
import android.view.Surface;
import android.view.View;
import android.view.WindowManager;
+import android.view.accessibility.CaptioningManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
@@ -295,6 +297,17 @@
public abstract boolean onTune(Uri channelUri);
/**
+ * Enables or disables the caption.
+ * <p>
+ * The locale for the user's preferred captioning language can be obtained by calling
+ * {@link CaptioningManager#getLocale CaptioningManager.getLocale()}.
+ *
+ * @param enabled {@code true} to enable, {@code false} to disable.
+ * @see CaptioningManager
+ */
+ public abstract void onSetCaptionEnabled(boolean enabled);
+
+ /**
* Selects a given track.
* <p>
* If it is called multiple times on the same type of track (ie. Video, Audio, Text), the
@@ -302,9 +315,10 @@
* Also, if the select operation was successful, the implementation should call
* {@link #dispatchTrackInfoChanged(List)} to report the updated track information.
* </p>
+ *
* @param track The track to be selected.
* @return {@code true} if the select operation was successful, {@code false} otherwise.
- * @see #dispatchTrackInfoChanged(TvTrackInfo[])
+ * @see #dispatchTrackInfoChanged
* @see TvTrackInfo#KEY_IS_SELECTED
*/
public boolean onSelectTrack(TvTrackInfo track) {
@@ -317,9 +331,10 @@
* If the unselect operation was successful, the implementation should call
* {@link #dispatchTrackInfoChanged(List)} to report the updated track information.
* </p>
+ *
* @param track The track to be unselected.
* @return {@code true} if the unselect operation was successful, {@code false} otherwise.
- * @see #dispatchTrackInfoChanged(TvTrackInfo[])
+ * @see #dispatchTrackInfoChanged
* @see TvTrackInfo#KEY_IS_SELECTED
*/
public boolean onUnselectTrack(TvTrackInfo track) {
@@ -492,6 +507,13 @@
}
/**
+ * Calls {@link #onSetCaptionEnabled}.
+ */
+ void setCaptionEnabled(boolean enabled) {
+ onSetCaptionEnabled(enabled);
+ }
+
+ /**
* Calls {@link #onSelectTrack}.
*/
void selectTrack(TvTrackInfo track) {
@@ -656,6 +678,7 @@
return false;
}
+ @SuppressLint("HandlerLeak")
private final class ServiceHandler extends Handler {
private static final int DO_CREATE_SESSION = 1;
private static final int DO_BROADCAST_AVAILABILITY_CHANGE = 2;
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 10f0c6b..7eb27b6 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.graphics.Rect;
-import android.media.MediaPlayer.TrackInfo;
import android.media.tv.TvInputManager.Session;
import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
import android.media.tv.TvInputManager.SessionCallback;
@@ -216,11 +215,26 @@
}
/**
+ * Enables or disables the caption in this TvView.
+ * <p>
+ * Note that this method does not take any effect unless the current TvView is tuned.
+ *
+ * @param enabled {@code true} to enable, {@code false} to disable.
+ */
+ public void setCaptionEnabled(boolean enabled) {
+ if (mSession != null) {
+ mSession.setCaptionEnabled(enabled);
+ }
+ }
+
+ /**
* Select a track.
* <p>
* If it is called multiple times on the same type of track (ie. Video, Audio, Text), the track
- * selected in previous will be unselected.
+ * selected in previous will be unselected. Note that this method does not take any effect
+ * unless the current TvView is tuned.
* </p>
+ *
* @param track the track to be selected.
* @see #getTracks()
*/
@@ -232,6 +246,8 @@
/**
* Unselect a track.
+ * <p>
+ * Note that this method does not take any effect unless the current TvView is tuned.
*
* @param track the track to be unselected.
* @see #getTracks()
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 4a7c096..6a835d6a 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -54,7 +54,8 @@
};
enum {
- EVENT_NOTIFY = 1,
+ EVENT_CALLBACK = 1,
+ EVENT_SET_CALLBACK = 2,
};
struct CryptoErrorCodes {
@@ -82,9 +83,7 @@
JNIEnv *env, jobject thiz,
const char *name, bool nameIsType, bool encoder)
: mClass(NULL),
- mObject(NULL),
- mGeneration(1),
- mRequestedActivityNotification(false) {
+ mObject(NULL) {
jclass clazz = env->GetObjectClass(thiz);
CHECK(clazz != NULL);
@@ -151,6 +150,18 @@
mClass = NULL;
}
+status_t JMediaCodec::setCallback(jobject cb) {
+ if (cb != NULL) {
+ if (mCallbackNotification == NULL) {
+ mCallbackNotification = new AMessage(kWhatCallbackNotify, id());
+ }
+ } else {
+ mCallbackNotification.clear();
+ }
+
+ return mCodec->setCallback(mCallbackNotification);
+}
+
status_t JMediaCodec::configure(
const sp<AMessage> &format,
const sp<IGraphicBufferProducer> &bufferProducer,
@@ -173,32 +184,13 @@
}
status_t JMediaCodec::start() {
- status_t err = mCodec->start();
-
- if (err != OK) {
- return err;
- }
-
- mActivityNotification = new AMessage(kWhatActivityNotify, id());
- mActivityNotification->setInt32("generation", mGeneration);
-
- requestActivityNotification();
-
- return err;
+ return mCodec->start();
}
status_t JMediaCodec::stop() {
mSurfaceTextureClient.clear();
- status_t err = mCodec->stop();
-
- sp<AMessage> msg = new AMessage(kWhatStopActivityNotifications, id());
- sp<AMessage> response;
- msg->postAndAwaitResponse(&response);
-
- mActivityNotification.clear();
-
- return err;
+ return mCodec->stop();
}
status_t JMediaCodec::flush() {
@@ -230,11 +222,7 @@
}
status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
- status_t err = mCodec->dequeueInputBuffer(index, timeoutUs);
-
- requestActivityNotification();
-
- return err;
+ return mCodec->dequeueInputBuffer(index, timeoutUs);
}
status_t JMediaCodec::dequeueOutputBuffer(
@@ -245,8 +233,6 @@
status_t err = mCodec->dequeueOutputBuffer(
index, &offset, &size, &timeUs, &flags, timeoutUs);
- requestActivityNotification();
-
if (err != OK) {
return err;
}
@@ -274,10 +260,21 @@
return mCodec->signalEndOfInputStream();
}
-status_t JMediaCodec::getOutputFormat(JNIEnv *env, jobject *format) const {
+status_t JMediaCodec::getFormat(JNIEnv *env, bool input, jobject *format) const {
sp<AMessage> msg;
status_t err;
- if ((err = mCodec->getOutputFormat(&msg)) != OK) {
+ err = input ? mCodec->getInputFormat(&msg) : mCodec->getOutputFormat(&msg);
+ if (err != OK) {
+ return err;
+ }
+
+ return ConvertMessageToMap(env, msg, format);
+}
+
+status_t JMediaCodec::getOutputFormat(JNIEnv *env, size_t index, jobject *format) const {
+ sp<AMessage> msg;
+ status_t err;
+ if ((err = mCodec->getOutputFormat(index, &msg)) != OK) {
return err;
}
@@ -309,6 +306,11 @@
CHECK(orderID != NULL);
+ jmethodID asReadOnlyBufferID = env->GetMethodID(
+ byteBufferClass.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");
+
+ CHECK(asReadOnlyBufferID != NULL);
+
ScopedLocalRef<jclass> byteOrderClass(
env, env->FindClass("java/nio/ByteOrder"));
@@ -341,6 +343,12 @@
env->NewDirectByteBuffer(
buffer->base(),
buffer->capacity());
+ if (!input && byteBuffer != NULL) {
+ jobject readOnlyBuffer = env->CallObjectMethod(
+ byteBuffer, asReadOnlyBufferID);
+ env->DeleteLocalRef(byteBuffer);
+ byteBuffer = readOnlyBuffer;
+ }
if (byteBuffer == NULL) {
env->DeleteLocalRef(nativeByteOrderObj);
return NO_MEMORY;
@@ -363,6 +371,130 @@
return OK;
}
+status_t JMediaCodec::getBuffer(
+ JNIEnv *env, bool input, size_t index, jobject *buf) const {
+ sp<ABuffer> buffer;
+
+ status_t err =
+ input
+ ? mCodec->getInputBuffer(index, &buffer)
+ : mCodec->getOutputBuffer(index, &buffer);
+
+ if (err != OK) {
+ return err;
+ }
+
+ ScopedLocalRef<jclass> byteBufferClass(
+ env, env->FindClass("java/nio/ByteBuffer"));
+
+ CHECK(byteBufferClass.get() != NULL);
+
+ jmethodID orderID = env->GetMethodID(
+ byteBufferClass.get(),
+ "order",
+ "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;");
+
+ CHECK(orderID != NULL);
+
+ jmethodID asReadOnlyBufferID = env->GetMethodID(
+ byteBufferClass.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");
+
+ CHECK(asReadOnlyBufferID != NULL);
+
+ jmethodID positionID = env->GetMethodID(
+ byteBufferClass.get(), "position", "(I)Ljava/nio/Buffer;");
+
+ CHECK(positionID != NULL);
+
+ jmethodID limitID = env->GetMethodID(
+ byteBufferClass.get(), "limit", "(I)Ljava/nio/Buffer;");
+
+ CHECK(limitID != NULL);
+
+ ScopedLocalRef<jclass> byteOrderClass(
+ env, env->FindClass("java/nio/ByteOrder"));
+
+ CHECK(byteOrderClass.get() != NULL);
+
+ jmethodID nativeOrderID = env->GetStaticMethodID(
+ byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;");
+ CHECK(nativeOrderID != NULL);
+
+ jobject nativeByteOrderObj =
+ env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID);
+ CHECK(nativeByteOrderObj != NULL);
+
+ // if this is an ABuffer that doesn't actually hold any accessible memory,
+ // use a null ByteBuffer
+ if (buffer->base() == NULL) {
+ *buf = NULL;
+ return OK;
+ }
+
+ jobject byteBuffer =
+ env->NewDirectByteBuffer(
+ buffer->base(),
+ buffer->capacity());
+ if (!input && byteBuffer != NULL) {
+ jobject readOnlyBuffer = env->CallObjectMethod(
+ byteBuffer, asReadOnlyBufferID);
+ env->DeleteLocalRef(byteBuffer);
+ byteBuffer = readOnlyBuffer;
+ }
+ if (byteBuffer == NULL) {
+ env->DeleteLocalRef(nativeByteOrderObj);
+ return NO_MEMORY;
+ }
+ jobject me = env->CallObjectMethod(
+ byteBuffer, orderID, nativeByteOrderObj);
+ env->DeleteLocalRef(me);
+ me = env->CallObjectMethod(
+ byteBuffer, positionID,
+ input ? 0 : buffer->offset());
+ env->DeleteLocalRef(me);
+ me = env->CallObjectMethod(
+ byteBuffer, limitID,
+ input ? buffer->capacity() : (buffer->offset() + buffer->size()));
+ env->DeleteLocalRef(me);
+ me = NULL;
+
+ env->DeleteLocalRef(nativeByteOrderObj);
+ nativeByteOrderObj = NULL;
+
+ *buf = byteBuffer;
+ return OK;
+}
+
+status_t JMediaCodec::getImage(
+ JNIEnv *env, bool input, size_t index, jobject *buf) const {
+ sp<ABuffer> buffer;
+
+ status_t err =
+ input
+ ? mCodec->getInputBuffer(index, &buffer)
+ : mCodec->getOutputBuffer(index, &buffer);
+
+ if (err != OK) {
+ return err;
+ }
+
+ // if this is an ABuffer that doesn't actually hold any accessible memory,
+ // use a null ByteBuffer
+ *buf = NULL;
+ if (buffer->base() == NULL) {
+ return OK;
+ }
+
+ // check if buffer is an image
+ AString imageData;
+ if (!buffer->meta()->findString("image-data", &imageData)) {
+ return OK;
+ }
+
+ return OK;
+}
+
+
status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const {
AString name;
@@ -387,65 +519,121 @@
}
}
-void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
- switch (msg->what()) {
- case kWhatRequestActivityNotifications:
- {
- if (mRequestedActivityNotification) {
- break;
- }
+void JMediaCodec::handleCallback(const sp<AMessage> &msg) {
+ int32_t arg1, arg2 = 0;
+ jobject obj = NULL;
+ CHECK(msg->findInt32("callbackID", &arg1));
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
- mCodec->requestActivityNotification(mActivityNotification);
- mRequestedActivityNotification = true;
+ switch (arg1) {
+ case MediaCodec::CB_INPUT_AVAILABLE:
+ {
+ CHECK(msg->findInt32("index", &arg2));
break;
}
- case kWhatActivityNotify:
+ case MediaCodec::CB_OUTPUT_AVAILABLE:
{
- {
- int32_t generation;
- CHECK(msg->findInt32("generation", &generation));
+ CHECK(msg->findInt32("index", &arg2));
- if (generation != mGeneration) {
- // stale
- break;
+ size_t size, offset;
+ int64_t timeUs;
+ uint32_t flags;
+ CHECK(msg->findSize("size", &size));
+ CHECK(msg->findSize("offset", &offset));
+ CHECK(msg->findInt64("timeUs", &timeUs));
+ CHECK(msg->findInt32("flags", (int32_t *)&flags));
+
+ ScopedLocalRef<jclass> clazz(
+ env, env->FindClass("android/media/MediaCodec$BufferInfo"));
+ jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "()V");
+ jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
+
+ obj = env->NewObject(clazz.get(), ctor);
+
+ if (obj == NULL) {
+ if (env->ExceptionCheck()) {
+ ALOGE("Could not create MediaCodec.BufferInfo.");
+ env->ExceptionClear();
}
-
- mRequestedActivityNotification = false;
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
}
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- env->CallVoidMethod(
- mObject,
- gFields.postEventFromNativeID,
- EVENT_NOTIFY,
- 0 /* arg1 */,
- 0 /* arg2 */,
- NULL /* obj */);
+ env->CallVoidMethod(obj, method, (jint)offset, (jint)size, timeUs, flags);
+ break;
+ }
+
+ case MediaCodec::CB_ERROR:
+ {
+ int32_t err, actionCode;
+ CHECK(msg->findInt32("err", &err));
+ CHECK(msg->findInt32("actionCode", &actionCode));
+
+ // use Integer object to pass the action code
+ ScopedLocalRef<jclass> clazz(
+ env, env->FindClass("android/media/MediaCodec$CodecException"));
+ jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "(IILjava/lang/String;)V");
+
+ AString str;
+ const char *detail = "Unknown error";
+ if (msg->findString("detail", &str)) {
+ detail = str.c_str();
+ }
+ jstring msgObj = env->NewStringUTF(detail);
+
+ obj = env->NewObject(clazz.get(), ctor, err, actionCode, msgObj);
+
+ if (obj == NULL) {
+ if (env->ExceptionCheck()) {
+ ALOGE("Could not create CodecException object.");
+ env->ExceptionClear();
+ }
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
break;
}
- case kWhatStopActivityNotifications:
+ case MediaCodec::CB_OUTPUT_FORMAT_CHANGED:
{
- uint32_t replyID;
- CHECK(msg->senderAwaitsResponse(&replyID));
+ sp<AMessage> format;
+ CHECK(msg->findMessage("format", &format));
- ++mGeneration;
- mRequestedActivityNotification = false;
+ if (OK != ConvertMessageToMap(env, format, &obj)) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
- sp<AMessage> response = new AMessage;
- response->postReply(replyID);
break;
}
default:
TRESPASS();
}
+
+ env->CallVoidMethod(
+ mObject,
+ gFields.postEventFromNativeID,
+ EVENT_CALLBACK,
+ arg1,
+ arg2,
+ obj);
+
+ env->DeleteLocalRef(obj);
}
-void JMediaCodec::requestActivityNotification() {
- (new AMessage(kWhatRequestActivityNotifications, id()))->post();
+void JMediaCodec::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatCallbackNotify:
+ {
+ handleCallback(msg);
+ break;
+ }
+ default:
+ TRESPASS();
+ }
}
} // namespace android
@@ -551,6 +739,22 @@
return 0;
}
+static void android_media_MediaCodec_native_setCallback(
+ JNIEnv *env,
+ jobject thiz,
+ jobject cb) {
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ status_t err = codec->setCallback(cb);
+
+ throwExceptionAsNecessary(env, err);
+}
+
static void android_media_MediaCodec_native_configure(
JNIEnv *env,
jobject thiz,
@@ -908,9 +1112,9 @@
throwExceptionAsNecessary(env, err);
}
-static jobject android_media_MediaCodec_getOutputFormatNative(
- JNIEnv *env, jobject thiz) {
- ALOGV("android_media_MediaCodec_getOutputFormatNative");
+static jobject android_media_MediaCodec_getFormatNative(
+ JNIEnv *env, jobject thiz, jboolean input) {
+ ALOGV("android_media_MediaCodec_getFormatNative");
sp<JMediaCodec> codec = getMediaCodec(env, thiz);
@@ -920,7 +1124,30 @@
}
jobject format;
- status_t err = codec->getOutputFormat(env, &format);
+ status_t err = codec->getFormat(env, input, &format);
+
+ if (err == OK) {
+ return format;
+ }
+
+ throwExceptionAsNecessary(env, err);
+
+ return NULL;
+}
+
+static jobject android_media_MediaCodec_getOutputFormatForIndexNative(
+ JNIEnv *env, jobject thiz, jint index) {
+ ALOGV("android_media_MediaCodec_getOutputFormatForIndexNative");
+
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ jobject format;
+ status_t err = codec->getOutputFormat(env, index, &format);
if (err == OK) {
return format;
@@ -957,6 +1184,58 @@
return NULL;
}
+static jobject android_media_MediaCodec_getBuffer(
+ JNIEnv *env, jobject thiz, jboolean input, jint index) {
+ ALOGV("android_media_MediaCodec_getBuffer");
+
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ jobject buffer;
+ status_t err = codec->getBuffer(env, input, index, &buffer);
+
+ if (err == OK) {
+ return buffer;
+ }
+
+ // if we're out of memory, an exception was already thrown
+ if (err != NO_MEMORY) {
+ throwExceptionAsNecessary(env, err);
+ }
+
+ return NULL;
+}
+
+static jobject android_media_MediaCodec_getImage(
+ JNIEnv *env, jobject thiz, jboolean input, jint index) {
+ ALOGV("android_media_MediaCodec_getImage");
+
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return NULL;
+ }
+
+ jobject image;
+ status_t err = codec->getImage(env, input, index, &image);
+
+ if (err == OK) {
+ return image;
+ }
+
+ // if we're out of memory, an exception was already thrown
+ if (err != NO_MEMORY) {
+ throwExceptionAsNecessary(env, err);
+ }
+
+ return NULL;
+}
+
static jobject android_media_MediaCodec_getName(
JNIEnv *env, jobject thiz) {
ALOGV("android_media_MediaCodec_getName");
@@ -1117,7 +1396,11 @@
}
static JNINativeMethod gMethods[] = {
- { "release", "()V", (void *)android_media_MediaCodec_release },
+ { "native_release", "()V", (void *)android_media_MediaCodec_release },
+
+ { "native_setCallback",
+ "(Landroid/media/MediaCodec$Callback;)V",
+ (void *)android_media_MediaCodec_native_setCallback },
{ "native_configure",
"([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;"
@@ -1127,20 +1410,20 @@
{ "createInputSurface", "()Landroid/view/Surface;",
(void *)android_media_MediaCodec_createInputSurface },
- { "start", "()V", (void *)android_media_MediaCodec_start },
+ { "native_start", "()V", (void *)android_media_MediaCodec_start },
{ "native_stop", "()V", (void *)android_media_MediaCodec_stop },
- { "flush", "()V", (void *)android_media_MediaCodec_flush },
+ { "native_flush", "()V", (void *)android_media_MediaCodec_flush },
- { "queueInputBuffer", "(IIIJI)V",
+ { "native_queueInputBuffer", "(IIIJI)V",
(void *)android_media_MediaCodec_queueInputBuffer },
- { "queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V",
+ { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V",
(void *)android_media_MediaCodec_queueSecureInputBuffer },
- { "dequeueInputBuffer", "(J)I",
+ { "native_dequeueInputBuffer", "(J)I",
(void *)android_media_MediaCodec_dequeueInputBuffer },
- { "dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I",
+ { "native_dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I",
(void *)android_media_MediaCodec_dequeueOutputBuffer },
{ "releaseOutputBuffer", "(IZZJ)V",
@@ -1149,12 +1432,21 @@
{ "signalEndOfInputStream", "()V",
(void *)android_media_MediaCodec_signalEndOfInputStream },
- { "getOutputFormatNative", "()Ljava/util/Map;",
- (void *)android_media_MediaCodec_getOutputFormatNative },
+ { "getFormatNative", "(Z)Ljava/util/Map;",
+ (void *)android_media_MediaCodec_getFormatNative },
+
+ { "getOutputFormatNative", "(I)Ljava/util/Map;",
+ (void *)android_media_MediaCodec_getOutputFormatForIndexNative },
{ "getBuffers", "(Z)[Ljava/nio/ByteBuffer;",
(void *)android_media_MediaCodec_getBuffers },
+ { "getBuffer", "(ZI)Ljava/nio/ByteBuffer;",
+ (void *)android_media_MediaCodec_getBuffer },
+
+ { "getImage", "(ZI)Landroid/media/Image;",
+ (void *)android_media_MediaCodec_getImage },
+
{ "getName", "()Ljava/lang/String;",
(void *)android_media_MediaCodec_getName },
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index bf9f4ea..2e650e3 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -44,6 +44,8 @@
void registerSelf();
void release();
+ status_t setCallback(jobject cb);
+
status_t configure(
const sp<AMessage> &format,
const sp<IGraphicBufferProducer> &bufferProducer,
@@ -84,11 +86,19 @@
status_t signalEndOfInputStream();
- status_t getOutputFormat(JNIEnv *env, jobject *format) const;
+ status_t getFormat(JNIEnv *env, bool input, jobject *format) const;
+
+ status_t getOutputFormat(JNIEnv *env, size_t index, jobject *format) const;
status_t getBuffers(
JNIEnv *env, bool input, jobjectArray *bufArray) const;
+ status_t getBuffer(
+ JNIEnv *env, bool input, size_t index, jobject *buf) const;
+
+ status_t getImage(
+ JNIEnv *env, bool input, size_t index, jobject *image) const;
+
status_t getName(JNIEnv *env, jstring *name) const;
status_t setParameters(const sp<AMessage> ¶ms);
@@ -99,12 +109,11 @@
virtual ~JMediaCodec();
virtual void onMessageReceived(const sp<AMessage> &msg);
+ void handleCallback(const sp<AMessage> &msg);
private:
enum {
- kWhatActivityNotify,
- kWhatRequestActivityNotifications,
- kWhatStopActivityNotifications,
+ kWhatCallbackNotify,
};
jclass mClass;
@@ -114,11 +123,7 @@
sp<ALooper> mLooper;
sp<MediaCodec> mCodec;
- sp<AMessage> mActivityNotification;
- int32_t mGeneration;
- bool mRequestedActivityNotification;
-
- void requestActivityNotification();
+ sp<AMessage> mCallbackNotification;
DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
};
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
index d01f4ec..38d7e3e 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
@@ -56,6 +56,7 @@
public static final String VIDEO_H263_AMR = "/sdcard/media_api/video/H263_56_AMRNB_6.3gp";
public static final String VIDEO_H264_AAC = "/sdcard/media_api/video/H264_320_AAC_64.3gp";
public static final String VIDEO_H264_AMR = "/sdcard/media_api/video/H264_320_AMRNB_6.3gp";
+ public static final String VIDEO_HEVC_AAC = "/sdcard/media_api/video/HEVC_320_AAC_128.mp4";
public static final String VIDEO_HIGHRES_H263 = "/sdcard/media_api/video/H263_500_AMRNB_12.3gp";
public static final String VIDEO_HIGHRES_MP4 = "/sdcard/media_api/video/H264_500_AAC_128.3gp";
public static final String VIDEO_WEBM = "/sdcard/media_api/video/big-buck-bunny_trailer.webm";
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index 7b2a20e..244b07f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -417,6 +417,21 @@
assertTrue("H264 playback memory test", memoryResult);
}
+ // Test case 3: Capture the memory usage after every 20 hevc playback
+ @LargeTest
+ public void testHEVCVideoPlaybackMemoryUsage() throws Exception {
+ boolean memoryResult = false;
+
+ mStartPid = getMediaserverPid();
+ for (int i = 0; i < NUM_STRESS_LOOP; i++) {
+ mediaStressPlayback(MediaNames.VIDEO_HEVC_AAC);
+ getMemoryWriteToLog(i);
+ writeProcmemInfo();
+ }
+ memoryResult = validateMemoryResult(mStartPid, mStartMemory, DECODER_LIMIT);
+ assertTrue("HEVC playback memory test", memoryResult);
+ }
+
// Test case 4: Capture the memory usage after every 20 video only recorded
@LargeTest
public void testH263RecordVideoOnlyMemoryUsage() throws Exception {
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java
index d90a4bc..9dd2732 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RangeTest.java
@@ -137,12 +137,12 @@
}
private static <T extends Comparable<? super T>> void assertInRange(Range<T> object, T needle) {
- assertAction("in-range", object, needle, true, object.inRange(needle));
+ assertAction("in-range", object, needle, true, object.contains(needle));
}
private static <T extends Comparable<? super T>> void assertOutOfRange(Range<T> object,
T needle) {
- assertAction("out-of-range", object, needle, false, object.inRange(needle));
+ assertAction("out-of-range", object, needle, false, object.contains(needle));
}
private static <T extends Comparable<? super T>> void assertUpper(Range<T> object, T expected) {
diff --git a/media/tests/contents/media_api/HEVC_320_AAC_128.mp4 b/media/tests/contents/media_api/HEVC_320_AAC_128.mp4
new file mode 100644
index 0000000..e3e3ef0
--- /dev/null
+++ b/media/tests/contents/media_api/HEVC_320_AAC_128.mp4
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png
deleted file mode 100644
index 88e8495..0000000
--- a/packages/PrintSpooler/res/drawable-hdpi/print_button_background.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png
deleted file mode 100644
index 3a37b27..0000000
--- a/packages/PrintSpooler/res/drawable-mdpi/print_button_background.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png b/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png
deleted file mode 100644
index b2ed8cd..0000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/print_button_background.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable/print_button_background.xml b/packages/PrintSpooler/res/drawable/print_button_background.xml
new file mode 100644
index 0000000..7b9aea5
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/print_button_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <solid
+ android:color="#FF00E5FF">
+ </solid>
+
+ <size
+ android:width="48dp"
+ android:height="48dp">
+ </size>
+
+</shape>
+
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
index 01cf9c1..8896a7b 100644
--- a/packages/PrintSpooler/res/layout/print_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -28,7 +28,7 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="16dip"
- android:elevation="8dip"
+ android:elevation="@dimen/preview_controls_elevation"
android:background="?android:attr/colorForegroundInverse">
<Spinner
@@ -50,7 +50,7 @@
android:paddingStart="16dip"
android:paddingEnd="16dip"
android:orientation="horizontal"
- android:elevation="8dip"
+ android:elevation="@dimen/preview_controls_elevation"
android:background="?android:attr/colorForegroundInverse">
<TextView
@@ -99,278 +99,16 @@
<ImageButton
android:id="@+id/print_button"
- style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dip"
- android:elevation="8dip"
+ android:elevation="@dimen/preview_controls_elevation"
android:background="@drawable/print_button">
</ImageButton>
<!-- Controls -->
- <LinearLayout
- android:id="@+id/dynamic_content"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:elevation="8dip"
- android:background="?android:attr/colorForegroundInverse">
-
- <LinearLayout
- android:id="@+id/draggable_content"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <com.android.printspooler.widget.PrintOptionsLayout
- android:id="@+id/options_container"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- printspooler:columnCount="@integer/print_option_column_count">
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Copies -->
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:labelFor="@+id/copies_edittext"
- android:text="@string/label_copies">
- </TextView>
-
- <view
- class="com.android.printspooler.widget.FirstFocusableEditText"
- android:id="@+id/copies_edittext"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="?android:attr/editTextStyle"
- android:inputType="numberDecimal">
- </view>
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Paper size -->
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:labelFor="@+id/paper_size_spinner"
- android:text="@string/label_paper_size">
- </TextView>
-
- <Spinner
- android:id="@+id/paper_size_spinner"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="@style/PrintOptionSpinnerStyle">
- </Spinner>
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Color -->
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:labelFor="@+id/color_spinner"
- android:text="@string/label_color">
- </TextView>
-
- <Spinner
- android:id="@+id/color_spinner"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="@style/PrintOptionSpinnerStyle">
- </Spinner>
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Orientation -->
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:labelFor="@+id/orientation_spinner"
- android:text="@string/label_orientation">
- </TextView>
-
- <Spinner
- android:id="@+id/orientation_spinner"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="@style/PrintOptionSpinnerStyle">
- </Spinner>
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Range options -->
-
- <TextView
- android:id="@+id/range_options_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:labelFor="@+id/range_options_spinner"
- android:text="@string/page_count_unknown">
- </TextView>
-
- <Spinner
- android:id="@+id/range_options_spinner"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- style="@style/PrintOptionSpinnerStyle">
- </Spinner>
-
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:orientation="vertical">
-
- <!-- Pages -->
-
- <TextView
- android:id="@+id/page_range_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginStart="12dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:text="@string/pages_range_example"
- android:labelFor="@+id/page_range_edittext"
- android:textAllCaps="false"
- android:visibility="visible">
- </TextView>
-
- <view
- class="com.android.printspooler.widget.FirstFocusableEditText"
- android:id="@+id/page_range_edittext"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom|fill_horizontal"
- style="@style/PrintOptionEditTextStyle"
- android:visibility="visible"
- android:inputType="textNoSuggestions">
- </view>
-
- </LinearLayout>
-
- </com.android.printspooler.widget.PrintOptionsLayout>
-
- <!-- More options -->
-
- <LinearLayout
- android:id="@+id/more_options_container"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:paddingStart="28dip"
- android:paddingEnd="28dip"
- android:orientation="vertical"
- android:visibility="visible">
-
- <ImageView
- android:layout_width="fill_parent"
- android:layout_height="1dip"
- android:layout_gravity="fill_horizontal"
- android:background="?android:attr/colorControlNormal"
- android:contentDescription="@null">
- </ImageView>
-
- <Button
- android:id="@+id/more_options_button"
- style="?android:attr/borderlessButtonStyle"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="fill_horizontal"
- android:text="@string/more_options_button"
- android:gravity="start|center_vertical"
- android:textAllCaps="false">
- </Button>
-
- <ImageView
- android:layout_width="fill_parent"
- android:layout_height="1dip"
- android:layout_gravity="fill_horizontal"
- android:background="?android:attr/colorControlNormal"
- android:contentDescription="@null">
- </ImageView>
-
- </LinearLayout>
-
- </LinearLayout>
-
- <!-- Expand/collapse handle -->
-
- <FrameLayout
- android:id="@+id/expand_collapse_handle"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/expand_collapse_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginBottom="8dip"
- android:layout_gravity="center"
- android:background="@drawable/ic_expand_more">
- </ImageView>
-
- </FrameLayout>
-
- </LinearLayout>
+ <include layout="@layout/print_activity_controls"/>
<!-- Content -->
diff --git a/packages/PrintSpooler/res/layout/print_activity_controls.xml b/packages/PrintSpooler/res/layout/print_activity_controls.xml
new file mode 100644
index 0000000..2da0714
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/print_activity_controls.xml
@@ -0,0 +1,281 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:printspooler="http://schemas.android.com/apk/res/com.android.printspooler"
+ android:id="@+id/dynamic_content"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:elevation="@dimen/preview_controls_elevation"
+ android:background="?android:attr/colorForegroundInverse">
+
+ <LinearLayout
+ android:id="@+id/draggable_content"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <com.android.printspooler.widget.PrintOptionsLayout
+ android:id="@+id/options_container"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ printspooler:columnCount="@integer/print_option_column_count">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Copies -->
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:labelFor="@+id/copies_edittext"
+ android:text="@string/label_copies">
+ </TextView>
+
+ <view
+ class="com.android.printspooler.widget.FirstFocusableEditText"
+ android:id="@+id/copies_edittext"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="?android:attr/editTextStyle"
+ android:inputType="numberDecimal">
+ </view>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Paper size -->
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:labelFor="@+id/paper_size_spinner"
+ android:text="@string/label_paper_size">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/paper_size_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="@style/PrintOptionSpinnerStyle">
+ </Spinner>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Color -->
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:labelFor="@+id/color_spinner"
+ android:text="@string/label_color">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/color_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="@style/PrintOptionSpinnerStyle">
+ </Spinner>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Orientation -->
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:labelFor="@+id/orientation_spinner"
+ android:text="@string/label_orientation">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/orientation_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="@style/PrintOptionSpinnerStyle">
+ </Spinner>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Range options -->
+
+ <TextView
+ android:id="@+id/range_options_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:labelFor="@+id/range_options_spinner"
+ android:text="@string/page_count_unknown">
+ </TextView>
+
+ <Spinner
+ android:id="@+id/range_options_spinner"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ style="@style/PrintOptionSpinnerStyle">
+ </Spinner>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
+ android:orientation="vertical">
+
+ <!-- Pages -->
+
+ <TextView
+ android:id="@+id/page_range_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginStart="12dip"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/pages_range_example"
+ android:labelFor="@+id/page_range_edittext"
+ android:textAllCaps="false"
+ android:visibility="visible">
+ </TextView>
+
+ <view
+ class="com.android.printspooler.widget.FirstFocusableEditText"
+ android:id="@+id/page_range_edittext"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|fill_horizontal"
+ style="@style/PrintOptionEditTextStyle"
+ android:visibility="visible"
+ android:inputType="textNoSuggestions">
+ </view>
+
+ </LinearLayout>
+
+ </com.android.printspooler.widget.PrintOptionsLayout>
+
+ <!-- More options -->
+
+ <LinearLayout
+ android:id="@+id/more_options_container"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="28dip"
+ android:paddingEnd="28dip"
+ android:orientation="vertical"
+ android:visibility="visible">
+
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:layout_gravity="fill_horizontal"
+ android:background="?android:attr/colorControlNormal"
+ android:contentDescription="@null">
+ </ImageView>
+
+ <Button
+ android:id="@+id/more_options_button"
+ style="?android:attr/borderlessButtonStyle"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="fill_horizontal"
+ android:text="@string/more_options_button"
+ android:gravity="start|center_vertical"
+ android:textAllCaps="false">
+ </Button>
+
+ <ImageView
+ android:layout_width="fill_parent"
+ android:layout_height="1dip"
+ android:layout_gravity="fill_horizontal"
+ android:background="?android:attr/colorControlNormal"
+ android:contentDescription="@null">
+ </ImageView>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <!-- Expand/collapse handle -->
+
+ <FrameLayout
+ android:id="@+id/expand_collapse_handle"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/expand_collapse_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
+ android:layout_gravity="center"
+ android:background="@drawable/ic_expand_more">
+ </ImageView>
+
+ </FrameLayout>
+
+</LinearLayout>
+
diff --git a/packages/PrintSpooler/res/values-land/constants.xml b/packages/PrintSpooler/res/values-land/constants.xml
index e84a32be..a4666a5 100644
--- a/packages/PrintSpooler/res/values-land/constants.xml
+++ b/packages/PrintSpooler/res/values-land/constants.xml
@@ -17,7 +17,11 @@
<resources>
<dimen name="printer_list_view_padding_start">48dip</dimen>
+
<dimen name="printer_list_view_padding_end">48dip</dimen>
+
<integer name="preview_page_per_row_count">2</integer>
+ <integer name="print_option_column_count">3</integer>
+
</resources>
diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml
index a19cd65..c17c73b 100644
--- a/packages/PrintSpooler/res/values/constants.xml
+++ b/packages/PrintSpooler/res/values/constants.xml
@@ -38,4 +38,8 @@
<dimen name="preview_list_padding">24dip</dimen>
+ <dimen name="preview_controls_elevation">8dip</dimen>
+
+ <integer name="print_option_column_count">2</integer>
+
</resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
index 63b4d96..3678cf2 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
@@ -26,7 +26,6 @@
import android.graphics.drawable.BitmapDrawable;
import android.graphics.pdf.PdfRenderer;
import android.os.AsyncTask;
-import android.os.Debug;
import android.os.ParcelFileDescriptor;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Margins;
@@ -44,7 +43,7 @@
public final class PageContentRepository {
private static final String LOG_TAG = "PageContentRepository";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private static final int INVALID_PAGE_INDEX = -1;
@@ -69,7 +68,6 @@
private RenderSpec mLastRenderSpec;
private int mScheduledPreloadFirstShownPage = INVALID_PAGE_INDEX;
-
private int mScheduledPreloadLastShownPage = INVALID_PAGE_INDEX;
private int mState;
@@ -525,7 +523,8 @@
* BYTES_PER_PIXEL;
final int maxCachedPageCount = mPageContentCache.getMaxSizeInBytes()
/ bitmapSizeInBytes;
- final int halfPreloadCount = (maxCachedPageCount - (lastShownPage - firstShownPage)) /2;
+ final int halfPreloadCount = (maxCachedPageCount
+ - (lastShownPage - firstShownPage)) / 2 - 1;
final int excessFromStart;
if (firstShownPage - halfPreloadCount < 0) {
@@ -713,36 +712,6 @@
break;
}
-// if (mRenderedPage == null) {
-// final int bitmapSizeInBytes = mRenderSpec.bitmapWidth
-// * mRenderSpec.bitmapHeight * BYTES_PER_PIXEL;
-//
-// while (mPageContentCache.getSizeInBytes() > 0
-// && mPageContentCache.getSizeInBytes() + bitmapSizeInBytes
-// > mPageContentCache.getMaxSizeInBytes()) {
-// RenderedPage renderedPage = mPageContentCache.removeLeastNeeded();
-//
-// // If not the right size - recycle and keep freeing space.
-// Bitmap bitmap = renderedPage.content.getBitmap();
-// if (!mRenderSpec.hasSameSize(bitmap.getWidth(), bitmap.getHeight())) {
-// if (DEBUG) {
-// Log.i(LOG_TAG, "Recycling bitmap for page: " + mPageIndex
-// + " with different size.");
-// }
-// bitmap.recycle();
-// continue;
-// }
-//
-// mRenderedPage = renderedPage;
-// bitmap.eraseColor(Color.WHITE);
-//
-// if (DEBUG) {
-// Log.i(LOG_TAG, "Reused bitmap for page: " + mPageIndex + " cache size: "
-// + mPageContentCache.getSizeInBytes() + " bytes");
-// }
-// }
-// }
-
if (mRenderedPage == null) {
if (DEBUG) {
Log.i(LOG_TAG, "Created bitmap for page: " + mPageIndex + " cache size: "
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index 09ce4e1..8885a7b 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -68,54 +68,42 @@
private final CloseGuard mCloseGuard = CloseGuard.get();
private final SparseArray<Void> mBoundPagesInAdapter = new SparseArray<>();
-
private final SparseArray<Void> mConfirmedPagesInDocument = new SparseArray<>();
private final PageClickListener mPageClickListener = new PageClickListener();
+ private final Context mContext;
private final LayoutInflater mLayoutInflater;
- private final Context mContext;
-
private final ContentUpdateRequestCallback mContentUpdateRequestCallback;
-
private final PageContentRepository mPageContentRepository;
-
private final PreviewArea mPreviewArea;
- private int mDocumentPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
-
- private int mSelectedPageCount;
-
// Which document pages to be written.
private PageRange[] mRequestedPages;
-
// Pages written in the current file.
private PageRange[] mWrittenPages;
-
// Pages the user selected in the UI.
private PageRange[] mSelectedPages;
- private float mSelectedPageElevation;
+ private int mDocumentPageCount = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
+ private int mSelectedPageCount;
+ private float mSelectedPageElevation;
private float mUnselectedPageElevation;
private int mPreviewPageMargin;
-
private int mPreviewListPadding;
-
private int mFooterHeight;
private int mColumnCount;
private MediaSize mMediaSize;
-
private Margins mMinMargins;
private int mState;
private int mPageContentWidth;
-
private int mPageContentHeight;
public interface ContentUpdateRequestCallback {
@@ -720,17 +708,17 @@
}
public void startPreloadContent(PageRange pageRangeInAdapter) {
-// final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart());
-// final int startPageInFile = computePageIndexInFile(startPageInDocument);
-// final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd());
-// final int endPageInFile = computePageIndexInFile(endPageInDocument);
-// if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) {
-// mPageContentRepository.startPreload(startPageInFile, endPageInFile);
-// }
+ final int startPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getStart());
+ final int startPageInFile = computePageIndexInFile(startPageInDocument);
+ final int endPageInDocument = computePageIndexInDocument(pageRangeInAdapter.getEnd());
+ final int endPageInFile = computePageIndexInFile(endPageInDocument);
+ if (startPageInDocument != INVALID_PAGE_INDEX && endPageInDocument != INVALID_PAGE_INDEX) {
+ mPageContentRepository.startPreload(startPageInFile, endPageInFile);
+ }
}
public void stopPreloadContent() {
-// mPageContentRepository.stopPreload();
+ mPageContentRepository.stopPreload();
}
private void doDestroy() {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 7359221..5ec2111 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -268,7 +268,6 @@
mPrintPreviewController = new PrintPreviewController(PrintActivity.this,
fileProvider);
-
mPrintedDocument = new RemotePrintDocument(PrintActivity.this,
IPrintDocumentAdapter.Stub.asInterface(documentAdapter),
fileProvider, new RemotePrintDocument.DocumentObserver() {
@@ -277,23 +276,17 @@
finish();
}
}, PrintActivity.this);
-
mProgressMessageController = new ProgressMessageController(
PrintActivity.this);
-
mMediaSizeComparator = new MediaSizeComparator(PrintActivity.this);
-
mDestinationSpinnerAdapter = new DestinationAdapter();
bindUi();
-
updateOptionsUi();
// Now show the updated UI to avoid flicker.
mOptionsContent.setVisibility(View.VISIBLE);
-
mSelectedPages = computeSelectedPages();
-
mPrintedDocument.start();
ensurePreviewUiShown();
@@ -345,7 +338,8 @@
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK
&& event.isTracking() && !event.isCanceled()) {
- if (mPrintPreviewController != null&&mPrintPreviewController.isOptionsOpened() && !hasErrors()) {
+ if (mPrintPreviewController != null&&mPrintPreviewController.isOptionsOpened()
+ && !hasErrors()) {
mPrintPreviewController.closeOptions();
} else {
cancelPrint();
@@ -364,8 +358,7 @@
@Override
public void onActionPerformed() {
- if (mState == STATE_UPDATE_FAILED
- && canUpdateDocument()) {
+ if (mState == STATE_UPDATE_FAILED && canUpdateDocument()) {
updateDocument(true, true);
ensurePreviewUiShown();
setState(STATE_CONFIGURING);
@@ -648,7 +641,8 @@
final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount();
MediaSize newMediaSizePortrait = newAttributes.getMediaSize().asPortrait();
for (int i = 0; i < mediaSizeCount; i++) {
- MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i).value.asPortrait();
+ MediaSize supportedSizePortrait = mMediaSizeSpinnerAdapter.getItem(i)
+ .value.asPortrait();
if (supportedSizePortrait.equals(newMediaSizePortrait)) {
currAttributes.setMediaSize(newMediaSize);
mMediaSizeSpinner.setSelection(i);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
index 910818bf..ce47b3e 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
@@ -45,22 +45,17 @@
class PrintPreviewController implements MutexFileProvider.OnReleaseRequestCallback,
PageAdapter.PreviewArea, EmbeddedContentContainer.OnSizeChangeListener {
- private PrintActivity mActivity;
+ private final PrintActivity mActivity;
private final MutexFileProvider mFileProvider;
-
private final MyHandler mHandler;
private final PageAdapter mPageAdapter;
-
private final StaggeredGridLayoutManager mLayoutManger;
- private PrintOptionsLayout mPrintOptionsLayout;
-
+ private final PrintOptionsLayout mPrintOptionsLayout;
private final RecyclerView mRecyclerView;
-
private final PrintContentView mContentView;
-
private final EmbeddedContentContainer mEmbeddedContentContainer;
private final PreloadController mPreloadController;
@@ -79,6 +74,7 @@
R.integer.preview_page_per_row_count);
mLayoutManger = new StaggeredGridLayoutManager(columnCount, OrientationHelper.VERTICAL);
+
mRecyclerView = (RecyclerView) activity.findViewById(R.id.preview_content);
mRecyclerView.setLayoutManager(mLayoutManger);
mRecyclerView.setAdapter(mPageAdapter);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
index bb63fb8..4d2cb6c 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
@@ -43,7 +43,6 @@
private PageContentProvider mProvider;
private MediaSize mMediaSize;
-
private Margins mMinMargins;
private boolean mContentRequested;
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index b4a78e6..afdbb2a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -37,6 +37,11 @@
public final class PrintContentView extends ViewGroup implements View.OnClickListener {
private static final int FIRST_POINTER_ID = 0;
+ private static final int ALPHA_MASK = 0xff000000;
+ private static final int ALPHA_SHIFT = 24;
+
+ private static final int COLOR_MASK = 0xffffff;
+
private final ViewDragHelper mDragger;
private final int mScrimColor;
@@ -187,9 +192,9 @@
}
private int computeScrimColor() {
- final int baseAlpha = (mScrimColor & 0xff000000) >>> 24;
+ final int baseAlpha = (mScrimColor & ALPHA_MASK) >>> ALPHA_SHIFT;
final int adjustedAlpha = (int) (baseAlpha * (1 - mDragProgress));
- return adjustedAlpha << 24 | (mScrimColor & 0xffffff);
+ return adjustedAlpha << ALPHA_SHIFT | (mScrimColor & COLOR_MASK);
}
private int getOpenedOptionsY() {
diff --git a/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.xml b/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.xml
new file mode 100644
index 0000000..e5737ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_lock_to_app_24dp.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="24.0dp"
+ android:height="24.0dp"/>
+
+ <viewport
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"/>
+
+ <path
+ android:fill="@color/recents_task_view_lock_to_app_button_color"
+ android:pathData="M18.0,8.0l-1.0,0.0L17.0,6.0c0.0,-2.8 -2.2,-5.0 -5.0,-5.0C9.2,1.0 7.0,3.2 7.0,6.0l0.0,2.0L6.0,8.0c-1.1,0.0 -2.0,0.9 -2.0,2.0l0.0,10.0c0.0,1.1 0.9,2.0 2.0,2.0l12.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L20.0,10.0C20.0,8.9 19.1,8.0 18.0,8.0zM12.0,17.0c-1.1,0.0 -2.0,-0.9 -2.0,-2.0s0.9,-2.0 2.0,-2.0c1.1,0.0 2.0,0.9 2.0,2.0S13.1,17.0 12.0,17.0zM15.1,8.0L8.9,8.0L8.9,6.0c0.0,-1.7 1.4,-3.1 3.1,-3.1c1.7,0.0 3.1,1.4 3.1,3.1L15.1,8.0z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml
new file mode 100644
index 0000000..aa0b369
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_bluetooth_detail_empty.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="56dp"
+ android:height="56dp"/>
+
+ <viewport
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
+
+ <path
+ android:fill="@color/qs_detail_empty"
+ android:pathData="M35.4,15.4L24.0,4.0l-2.0,0.0l0.0,15.2L12.8,10.0L10.0,12.8L21.2,24.0L10.0,35.2l2.8,2.8l9.2,-9.2L22.0,44.0l2.0,0.0l11.4,-11.4L26.8,24.0L35.4,15.4zM26.0,11.7l3.8,3.8L26.0,19.2L26.0,11.7zM29.8,32.6L26.0,36.3l0.0,-7.5L29.8,32.6z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_cancel.xml b/packages/SystemUI/res/drawable/ic_qs_cancel.xml
new file mode 100644
index 0000000..de72f13
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_cancel.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="24dp"
+ android:height="24dp"/>
+
+ <viewport
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
+
+ <path
+ android:fill="#FFFFFFFF"
+ android:pathData="M24.0,4.0C12.9,4.0 4.0,12.9 4.0,24.0s8.9,20.0 20.0,20.0c11.1,0.0 20.0,-8.9 20.0,-20.0S35.1,4.0 24.0,4.0zM34.0,31.2L31.2,34.0L24.0,26.8L16.8,34.0L14.0,31.2l7.2,-7.2L14.0,16.8l2.8,-2.8l7.2,7.2l7.2,-7.2l2.8,2.8L26.8,24.0L34.0,31.2z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml b/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml
new file mode 100644
index 0000000..16fa30b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_wifi_detail_empty.xml
@@ -0,0 +1,28 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android" >
+ <size
+ android:width="56dp"
+ android:height="56dp"/>
+
+ <viewport
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0"/>
+
+ <path
+ android:pathData="M24.0,4.0C15.0,4.0 6.7,7.0 0.0,12.0l24.0,32.0l24.0,-32.0C41.3,7.0 33.0,4.0 24.0,4.0z"
+ android:fill="@color/qs_detail_empty" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml b/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml
new file mode 100644
index 0000000..d38c8a4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/recents_lock_to_task_button_bg.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#ffdadada">
+ <item android:drawable="@color/recents_task_view_lock_to_app_button_background_color" />
+</ripple>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_item.xml b/packages/SystemUI/res/layout/qs_detail_item.xml
index c5eaed9..55139fb 100644
--- a/packages/SystemUI/res/layout/qs_detail_item.xml
+++ b/packages/SystemUI/res/layout/qs_detail_item.xml
@@ -17,9 +17,9 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/qs_detail_item_height"
- android:gravity="center_vertical"
android:background="@drawable/btn_borderless_rect"
android:clickable="true"
+ android:gravity="center_vertical"
android:orientation="horizontal" >
<ImageView
@@ -29,9 +29,10 @@
android:layout_marginEnd="12dp" />
<LinearLayout
- android:layout_width="match_parent"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
+ android:layout_weight="1"
android:orientation="vertical" >
<TextView
@@ -48,4 +49,13 @@
android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" />
</LinearLayout>
+ <ImageView
+ android:id="@android:id/icon2"
+ style="@style/QSBorderlessButton"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:clickable="true"
+ android:scaleType="center"
+ android:src="@drawable/ic_qs_cancel" />
+
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml
new file mode 100644
index 0000000..b64005f
--- /dev/null
+++ b/packages/SystemUI/res/layout/qs_detail_items.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- extends FrameLayout -->
+<com.android.systemui.qs.QSDetailItems xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+
+ <LinearLayout
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+
+ <LinearLayout
+ android:id="@android:id/empty"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center_horizontal"
+ android:orientation="vertical" >
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="56dp"
+ android:layout_height="56dp" />
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:textAppearance="@style/TextAppearance.QS.DetailEmpty" />
+ </LinearLayout>
+
+</com.android.systemui.qs.QSDetailItems>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index 2b50a95..7e8bfd32 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -62,6 +62,27 @@
android:visibility="invisible"
android:src="@drawable/recents_dismiss_light" />
</com.android.systemui.recents.views.TaskBarView>
+ <FrameLayout
+ android:id="@+id/lock_to_app"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/recents_task_view_lock_to_app_button_height"
+ android:layout_gravity="center_horizontal|bottom"
+ android:background="@drawable/recents_lock_to_task_button_bg"
+ android:visibility="invisible">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center"
+ android:drawableLeft="@drawable/ic_lock_to_app_24dp"
+ android:drawablePadding="8dp"
+ android:textSize="16sp"
+ android:textColor="@color/recents_task_view_lock_to_app_button_color"
+ android:text="@string/recents_lock_to_app_button_label"
+ android:fontFamily="sans-serif-medium"
+ android:singleLine="true"
+ android:textAllCaps="true" />
+ </FrameLayout>
</com.android.systemui.recents.views.TaskView>
diff --git a/packages/SystemUI/res/layout/split_clock_view.xml b/packages/SystemUI/res/layout/split_clock_view.xml
new file mode 100644
index 0000000..d9ba35d
--- /dev/null
+++ b/packages/SystemUI/res/layout/split_clock_view.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2014 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- Extends LinearLayout -->
+<com.android.systemui.statusbar.policy.SplitClockView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ >
+ <TextClock
+ android:id="@+id/time_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+ />
+ <TextClock
+ android:id="@+id/am_pm_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
+ />
+</com.android.systemui.statusbar.policy.SplitClockView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
index 8b79dbf..272904a 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -98,13 +98,8 @@
android:background="?android:attr/selectableItemBackground"
android:enabled="false"
>
- <com.android.systemui.statusbar.policy.Clock
+ <include layout="@layout/split_clock_view"
android:id="@+id/clock"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:singleLine="true"
- android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock"
- systemui:amPmStyle="normal"
/>
<com.android.systemui.statusbar.policy.DateView android:id="@+id/date_collapsed"
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index ef8302d..bae7ed7 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -41,6 +41,7 @@
<color name="qs_tile_divider">#29ffffff</color><!-- 16% white -->
<color name="qs_tile_text">#B3FFFFFF</color><!-- 70% white -->
<color name="qs_subhead">#66FFFFFF</color><!-- 40% white -->
+ <color name="qs_detail_empty">#24B0BEC5</color><!-- 14% blue grey 200-->
<color name="data_usage_secondary">#99FFFFFF</color><!-- 60% white -->
<color name="data_usage_graph_track">#33FFFFFF</color><!-- 20% white -->
<color name="status_bar_clock_color">#33FFFFFF</color>
@@ -53,13 +54,17 @@
<!-- The recents task bar light text color to be drawn on top of dark backgrounds. -->
<color name="recents_task_bar_light_text_color">#ffeeeeee</color>
<!-- The recents task bar dark text color to be drawn on top of light backgrounds. -->
- <color name="recents_task_bar_dark_text_color">#ff333333</color>
+ <color name="recents_task_bar_dark_text_color">#cc000000</color>
<!-- The recents task bar light dismiss icon color to be drawn on top of dark backgrounds. -->
<color name="recents_task_bar_light_dismiss_color">#ffeeeeee</color>
<!-- The recents task bar dark dismiss icon color to be drawn on top of light backgrounds. -->
- <color name="recents_task_bar_dark_dismiss_color">#ff333333</color>
+ <color name="recents_task_bar_dark_dismiss_color">#cc000000</color>
<!-- The recents task bar highlight color. -->
<color name="recents_task_bar_highlight_color">#28ffffff</color>
+ <!-- The lock to task button background color. -->
+ <color name="recents_task_view_lock_to_app_button_background_color">#ffe6e6e6</color>
+ <!-- The lock to task button foreground color. -->
+ <color name="recents_task_view_lock_to_app_button_color">#ff666666</color>
<color name="keyguard_affordance">#ffffffff</color>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 5c7dc908..94fcc23 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -127,6 +127,10 @@
<integer name="recents_animate_task_enter_from_home_duration">275</integer>
<!-- The animation stagger to apply to each task animation when transitioning from home. -->
<integer name="recents_animate_task_enter_from_home_delay">10</integer>
+ <!-- The short duration when animating in/out the lock to app button. -->
+ <integer name="recents_animate_lock_to_app_button_short_duration">150</integer>
+ <!-- The long duration when animating in/out the lock to app button. -->
+ <integer name="recents_animate_lock_to_app_button_long_duration">300</integer>
<!-- The min animation duration for animating the nav bar scrim in. -->
<integer name="recents_nav_bar_scrim_enter_duration">400</integer>
<!-- The animation duration for animating the removal of a task view. -->
@@ -157,5 +161,8 @@
<!-- Doze: should the significant motion sensor be used as a tease signal? -->
<bool name="doze_tease_on_significant_motion">true</bool>
+
+ <!-- Volume: time to delay dismissing the volume panel after a click is performed -->
+ <integer name="volume_panel_dismiss_delay">200</integer>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5400326..e86aa0a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -210,6 +210,9 @@
<!-- The amount of highlight to make on each task view. -->
<dimen name="recents_task_view_highlight">1dp</dimen>
+ <!-- The height of the lock-to-app button. -->
+ <dimen name="recents_task_view_lock_to_app_button_height">48dp</dimen>
+
<!-- The height of a task view bar. -->
<dimen name="recents_task_bar_height">56dp</dimen>
@@ -232,8 +235,9 @@
<!-- Space reserved for the cards behind the top card in the bottom stack -->
<dimen name="bottom_stack_peek_amount">12dp</dimen>
- <!-- bottom_stack_peek_amount + notification_min_height -->
- <dimen name="min_stack_height">76dp</dimen>
+ <!-- bottom_stack_peek_amount + notification_min_height
+ + notification_collapse_second_card_padding -->
+ <dimen name="min_stack_height">84dp</dimen>
<!-- The height of the area before the bottom stack in which the notifications slow down -->
<dimen name="bottom_stack_slow_down_length">12dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 848fdf8..b0f2133 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -486,6 +486,8 @@
<string name="quick_settings_bluetooth_multiple_devices_label">Bluetooth (<xliff:g id="number">%d</xliff:g> Devices)</string>
<!-- QuickSettings: Bluetooth (Off) [CHAR LIMIT=NONE] -->
<string name="quick_settings_bluetooth_off_label">Bluetooth Off</string>
+ <!-- QuickSettings: Bluetooth detail panel, text when there are no items [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_bluetooth_detail_empty_text">No paired devices available</string>
<!-- QuickSettings: Brightness [CHAR LIMIT=NONE] -->
<string name="quick_settings_brightness_label">Brightness</string>
<!-- QuickSettings: Rotation Unlocked [CHAR LIMIT=NONE] -->
@@ -522,6 +524,8 @@
<string name="quick_settings_wifi_no_network">No Network</string>
<!-- QuickSettings: Wifi (Off) [CHAR LIMIT=NONE] -->
<string name="quick_settings_wifi_off_label">Wi-Fi Off</string>
+ <!-- QuickSettings: Wifi detail panel, text when there are no items [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_wifi_detail_empty_text">No saved networks available</string>
<!-- QuickSettings: Remote display [CHAR LIMIT=NONE] -->
<string name="quick_settings_remote_display_no_connection_label">Cast screen</string>
<!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] -->
@@ -538,6 +542,8 @@
<string name="quick_settings_done">Done</string>
<!-- QuickSettings: Control panel: Label for connected device. [CHAR LIMIT=NONE] -->
<string name="quick_settings_connected">Connected</string>
+ <!-- QuickSettings: Control panel: Label for connecting device. [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_connecting">Connecting...</string>
<!-- QuickSettings: Tethering. [CHAR LIMIT=NONE] -->
<string name="quick_settings_tethering_label">Tethering</string>
<!-- QuickSettings: Hotspot. [CHAR LIMIT=NONE] -->
@@ -563,6 +569,8 @@
<string name="recents_empty_message">No recent apps</string>
<!-- Recents: The info panel app info button string. [CHAR LIMIT=NONE] -->
<string name="recents_app_info_button_label">Application Info</string>
+ <!-- Recents: The lock-to-app button. [CHAR LIMIT=NONE] -->
+ <string name="recents_lock_to_app_button_label">lock to app</string>
<!-- Recents: Temporary string for the button in the recents search bar. [CHAR LIMIT=NONE] -->
<string name="recents_search_bar_label">search</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f99b68b..7da6c22 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -179,6 +179,11 @@
<item name="android:gravity">center</item>
</style>
+ <style name="TextAppearance.QS.DetailEmpty">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">@color/qs_subhead</item>
+ </style>
+
<style name="TextAppearance.QS.Subhead">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif-medium</item>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
new file mode 100644
index 0000000..24c1378
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItems.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+/**
+ * Quick settings common detail view with line items.
+ */
+public class QSDetailItems extends FrameLayout {
+ private static final String TAG = "QSDetailItems";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+ private final H mHandler = new H();
+
+ private String mTag;
+ private Callback mCallback;
+ private boolean mItemsVisible = true;
+ private LinearLayout mItems;
+ private View mEmpty;
+ private TextView mEmptyText;
+ private ImageView mEmptyIcon;
+
+ public QSDetailItems(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ mTag = TAG;
+ }
+
+ public static QSDetailItems convertOrInflate(Context context, View convert, ViewGroup parent) {
+ if (convert instanceof QSDetailItems) {
+ return (QSDetailItems) convert;
+ }
+ return (QSDetailItems) LayoutInflater.from(context).inflate(R.layout.qs_detail_items,
+ parent, false);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mItems = (LinearLayout) findViewById(android.R.id.list);
+ mItems.setVisibility(GONE);
+ mEmpty = findViewById(android.R.id.empty);
+ mEmpty.setVisibility(GONE);
+ mEmptyText = (TextView) mEmpty.findViewById(android.R.id.title);
+ mEmptyIcon = (ImageView) mEmpty.findViewById(android.R.id.icon);
+ }
+
+ public void setTagSuffix(String suffix) {
+ mTag = TAG + "." + suffix;
+ }
+
+ public void setEmptyState(int icon, int text) {
+ mEmptyIcon.setImageResource(icon);
+ mEmptyText.setText(text);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (DEBUG) Log.d(mTag, "onAttachedToWindow");
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (DEBUG) Log.d(mTag, "onDetachedFromWindow");
+ mCallback = null;
+ }
+
+ public void setCallback(Callback callback) {
+ mHandler.removeMessages(H.SET_CALLBACK);
+ mHandler.obtainMessage(H.SET_CALLBACK, callback).sendToTarget();
+ }
+
+ public void setItems(Item[] items) {
+ mHandler.removeMessages(H.SET_ITEMS);
+ mHandler.obtainMessage(H.SET_ITEMS, items).sendToTarget();
+ }
+
+ public void setItemsVisible(boolean visible) {
+ mHandler.removeMessages(H.SET_ITEMS_VISIBLE);
+ mHandler.obtainMessage(H.SET_ITEMS_VISIBLE, visible ? 1 : 0, 0).sendToTarget();
+ }
+
+ private void handleSetCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ private void handleSetItems(Item[] items) {
+ final int itemCount = items != null ? items.length : 0;
+ mEmpty.setVisibility(itemCount == 0 ? VISIBLE : GONE);
+ mItems.setVisibility(itemCount == 0 ? GONE : VISIBLE);
+ for (int i = mItems.getChildCount() - 1; i >= itemCount; i--) {
+ mItems.removeViewAt(i);
+ }
+ for (int i = 0; i < itemCount; i++) {
+ bind(items[i], mItems.getChildAt(i));
+ }
+ }
+
+ private void handleSetItemsVisible(boolean visible) {
+ if (mItemsVisible == visible) return;
+ mItemsVisible = visible;
+ for (int i = 0; i < mItems.getChildCount(); i++) {
+ mItems.getChildAt(i).setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
+ }
+ }
+
+ private void bind(final Item item, View view) {
+ if (view == null) {
+ view = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item, this, false);
+ mItems.addView(view);
+ }
+ view.setVisibility(mItemsVisible ? VISIBLE : INVISIBLE);
+ final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
+ iv.setImageResource(item.icon);
+ final TextView title = (TextView) view.findViewById(android.R.id.title);
+ title.setText(item.line1);
+ final TextView summary = (TextView) view.findViewById(android.R.id.summary);
+ final boolean twoLines = !TextUtils.isEmpty(item.line2);
+ summary.setVisibility(twoLines ? VISIBLE : GONE);
+ summary.setText(twoLines ? item.line2 : null);
+ view.setMinimumHeight(mContext.getResources() .getDimensionPixelSize(
+ twoLines ? R.dimen.qs_detail_item_height_twoline : R.dimen.qs_detail_item_height));
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCallback != null) {
+ mCallback.onDetailItemClick(item);
+ }
+ }
+ });
+ final ImageView disconnect = (ImageView) view.findViewById(android.R.id.icon2);
+ disconnect.setVisibility(item.canDisconnect ? VISIBLE : GONE);
+ disconnect.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCallback != null) {
+ mCallback.onDetailItemDisconnect(item);
+ }
+ }
+ });
+ }
+
+ private class H extends Handler {
+ private static final int SET_ITEMS = 1;
+ private static final int SET_CALLBACK = 2;
+ private static final int SET_ITEMS_VISIBLE = 3;
+
+ public H() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what == SET_ITEMS) {
+ handleSetItems((Item[]) msg.obj);
+ } else if (msg.what == SET_CALLBACK) {
+ handleSetCallback((QSDetailItems.Callback) msg.obj);
+ } else if (msg.what == SET_ITEMS_VISIBLE) {
+ handleSetItemsVisible(msg.arg1 != 0);
+ }
+ }
+ }
+
+ public static class Item {
+ public int icon;
+ public String line1;
+ public String line2;
+ public Object tag;
+ public boolean canDisconnect;
+ }
+
+ public interface Callback {
+ void onDetailItemClick(Item item);
+ void onDetailItemDisconnect(Item item);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 36cd388..449cc1d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -168,6 +168,12 @@
fireToggleStateChanged(state);
}
}
+ @Override
+ public void onScanStateChanged(boolean state) {
+ if (mDetailRecord == r) {
+ fireScanStateChanged(state);
+ }
+ }
});
final View.OnClickListener click = new View.OnClickListener() {
@Override
@@ -195,6 +201,7 @@
if (mDetailRecord != null) return; // already showing something in detail
r.detailAdapter = r.tile.getDetailAdapter();
if (r.detailAdapter == null) return;
+ mDetailRecord = r;
r.detailView = r.detailAdapter.createDetailView(mContext, r.detailView, mDetailContent);
if (r.detailView == null) throw new IllegalStateException("Must return detail view");
mDetailDoneButton.setOnClickListener(new OnClickListener() {
@@ -211,7 +218,6 @@
mDetailRecord.tile.mHost.startSettingsActivity(settingsIntent);
}
});
- mDetailRecord = r;
mDetailContent.removeAllViews();
mDetail.bringToFront();
mDetailContent.addView(r.detailView);
@@ -312,6 +318,12 @@
}
}
+ private void fireScanStateChanged(boolean state) {
+ if (mCallback != null) {
+ mCallback.onScanStateChanged(state);
+ }
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
@@ -344,5 +356,6 @@
public interface Callback {
void onShowingDetail(QSTile.DetailAdapter detail);
void onToggleStateChanged(boolean state);
+ void onScanStateChanged(boolean state);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 62c9d9f..fc08cf4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -50,7 +50,7 @@
*/
public abstract class QSTile<TState extends State> implements Listenable {
protected final String TAG = "QSTile." + getClass().getSimpleName();
- protected static final boolean DEBUG = false;
+ protected static final boolean DEBUG = Log.isLoggable("QSTile", Log.DEBUG);
protected final Host mHost;
protected final Context mContext;
@@ -129,6 +129,10 @@
mHandler.obtainMessage(H.TOGGLE_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
}
+ public void fireScanStateChanged(boolean state) {
+ mHandler.obtainMessage(H.SCAN_STATE_CHANGED, state ? 1 : 0, 0).sendToTarget();
+ }
+
// call only on tile worker looper
private void handleSetCallback(Callback callback) {
@@ -166,6 +170,12 @@
}
}
+ private void handleScanStateChanged(boolean state) {
+ if (mCallback != null) {
+ mCallback.onScanStateChanged(state);
+ }
+ }
+
protected void handleUserSwitch(int newUserId) {
handleRefreshState(null);
}
@@ -178,6 +188,7 @@
private static final int SHOW_DETAIL = 5;
private static final int USER_SWITCH = 6;
private static final int TOGGLE_STATE_CHANGED = 7;
+ private static final int SCAN_STATE_CHANGED = 8;
private H(Looper looper) {
super(looper);
@@ -208,6 +219,9 @@
} else if (msg.what == TOGGLE_STATE_CHANGED) {
name = "handleToggleStateChanged";
handleToggleStateChanged(msg.arg1 != 0);
+ } else if (msg.what == SCAN_STATE_CHANGED) {
+ name = "handleScanStateChanged";
+ handleScanStateChanged(msg.arg1 != 0);
}
} catch (Throwable t) {
final String error = "Error in " + name;
@@ -221,6 +235,7 @@
void onStateChanged(State state);
void onShowDetail(boolean show);
void onToggleStateChanged(boolean state);
+ void onScanStateChanged(boolean state);
}
public interface Host {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 7431e69..1250d21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,24 +16,33 @@
package com.android.systemui.qs.tiles;
-import android.bluetooth.BluetoothAdapter.BluetoothStateChangeCallback;
+import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
import com.android.systemui.R;
+import com.android.systemui.qs.QSDetailItems;
+import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.BluetoothController.PairedDevice;
+
+import java.util.Set;
/** Quick settings tile: Bluetooth **/
public class BluetoothTile extends QSTile<QSTile.BooleanState> {
private static final Intent BLUETOOTH_SETTINGS = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
private final BluetoothController mController;
+ private final BluetoothDetailAdapter mDetailAdapter;
public BluetoothTile(Host host) {
super(host);
mController = host.getBluetoothController();
+ mDetailAdapter = new BluetoothDetailAdapter();
}
@Override
@@ -42,6 +51,11 @@
}
@Override
+ public DetailAdapter getDetailAdapter() {
+ return mDetailAdapter;
+ }
+
+ @Override
protected BooleanState newTileState() {
return new BooleanState();
}
@@ -63,7 +77,10 @@
@Override
protected void handleSecondaryClick() {
- mHost.startSettingsActivity(BLUETOOTH_SETTINGS);
+ if (!mState.value) {
+ mController.setBluetoothEnabled(true);
+ }
+ showDetail(true);
}
@Override
@@ -83,7 +100,8 @@
state.label = mController.getLastDeviceName();
} else if (connecting) {
state.iconId = R.drawable.ic_qs_bluetooth_connecting;
- stateContentDescription = mContext.getString(R.string.accessibility_desc_connecting);
+ stateContentDescription =
+ mContext.getString(R.string.accessibility_desc_connecting);
state.label = mContext.getString(R.string.quick_settings_bluetooth_label);
} else {
state.iconId = R.drawable.ic_qs_bluetooth_on;
@@ -106,5 +124,100 @@
public void onBluetoothStateChange(boolean enabled, boolean connecting) {
refreshState();
}
+ @Override
+ public void onBluetoothPairedDevicesChanged() {
+ mUiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mDetailAdapter.updateItems();
+ }
+ });
+ }
};
+
+ private final class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
+ private QSDetailItems mItems;
+
+ @Override
+ public int getTitle() {
+ return R.string.quick_settings_bluetooth_label;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ return mState.value;
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return BLUETOOTH_SETTINGS;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ mController.setBluetoothEnabled(state);
+ showDetail(false);
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
+ mItems.setTagSuffix("Bluetooth");
+ mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
+ R.string.quick_settings_bluetooth_detail_empty_text);
+ mItems.setCallback(this);
+ updateItems();
+ setItemsVisible(mState.value);
+ return mItems;
+ }
+
+ public void setItemsVisible(boolean visible) {
+ if (mItems == null) return;
+ mItems.setItemsVisible(visible);
+ }
+
+ private void updateItems() {
+ if (mItems == null) return;
+ Item[] items = null;
+ final Set<PairedDevice> devices = mController.getPairedDevices();
+ if (devices != null) {
+ items = new Item[devices.size()];
+ int i = 0;
+ for (PairedDevice device : devices) {
+ final Item item = new Item();
+ item.icon = R.drawable.ic_qs_bluetooth_on;
+ item.line1 = device.name;
+ if (device.state == PairedDevice.STATE_CONNECTED) {
+ item.icon = R.drawable.ic_qs_bluetooth_connected;
+ item.line2 = mContext.getString(R.string.quick_settings_connected);
+ item.canDisconnect = true;
+ } else if (device.state == PairedDevice.STATE_CONNECTING) {
+ item.icon = R.drawable.ic_qs_bluetooth_connecting;
+ item.line2 = mContext.getString(R.string.quick_settings_connecting);
+ }
+ item.tag = device;
+ items[i++] = item;
+ }
+ }
+ mItems.setItems(items);
+ }
+
+ @Override
+ public void onDetailItemClick(Item item) {
+ if (item == null || item.tag == null) return;
+ final PairedDevice device = (PairedDevice) item.tag;
+ if (device != null && device.state == PairedDevice.STATE_DISCONNECTED) {
+ mController.connect(device);
+ }
+ }
+
+ @Override
+ public void onDetailItemDisconnect(Item item) {
+ if (item == null || item.tag == null) return;
+ final PairedDevice device = (PairedDevice) item.tag;
+ if (device != null) {
+ mController.disconnect(device);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
index 2876607..900c7b26 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -21,15 +21,12 @@
import android.content.res.Resources;
import android.provider.Settings;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
import com.android.systemui.R;
+import com.android.systemui.qs.QSDetailItems;
+import com.android.systemui.qs.QSDetailItems.Item;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.qs.SignalTileView;
@@ -40,7 +37,6 @@
/** Quick settings tile: Wifi **/
public class WifiTile extends QSTile<QSTile.SignalState> {
private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
- private static final int MAX_ITEMS = 4; // TODO temporary visual restriction
private final NetworkController mController;
private final WifiDetailAdapter mDetailAdapter;
@@ -66,7 +62,6 @@
if (listening) {
mController.addNetworkSignalChangedCallback(mCallback);
mController.addAccessPointCallback(mDetailAdapter);
- mController.scanForAccessPoints();
} else {
mController.removeNetworkSignalChangedCallback(mCallback);
mController.removeAccessPointCallback(mDetailAdapter);
@@ -108,7 +103,7 @@
boolean wifiNotConnected = (cb.wifiSignalIconId > 0) && (cb.enabledDesc == null);
boolean enabledChanging = state.enabled != cb.enabled;
if (enabledChanging) {
- mDetailAdapter.postUpdateItems();
+ mDetailAdapter.setItemsVisible(cb.enabled);
fireToggleStateChanged(cb.enabled);
}
state.enabled = cb.enabled;
@@ -204,9 +199,9 @@
};
private final class WifiDetailAdapter implements DetailAdapter,
- NetworkController.AccessPointCallback {
+ NetworkController.AccessPointCallback, QSDetailItems.Callback {
- private LinearLayout mItems;
+ private QSDetailItems mItems;
private AccessPoint[] mAccessPoints;
@Override
@@ -232,66 +227,67 @@
@Override
public View createDetailView(Context context, View convertView, ViewGroup parent) {
- if (convertView != null) return convertView;
- mItems = new LinearLayout(context);
- mItems.setOrientation(LinearLayout.VERTICAL);
+ if (DEBUG) Log.d(TAG, "createDetailView convertView=" + (convertView != null));
+ mAccessPoints = null;
+ mController.scanForAccessPoints();
+ fireScanStateChanged(true);
+ mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
+ mItems.setTagSuffix("Wifi");
+ mItems.setCallback(this);
+ mItems.setEmptyState(R.drawable.ic_qs_wifi_detail_empty,
+ R.string.quick_settings_wifi_detail_empty_text);
updateItems();
+ setItemsVisible(mState.enabled);
return mItems;
}
@Override
public void onAccessPointsChanged(final AccessPoint[] accessPoints) {
- mUiHandler.post(new Runnable() {
- @Override
- public void run() {
- mAccessPoints = accessPoints;
- updateItems();
- }
- });
+ mAccessPoints = accessPoints;
+ updateItems();
+ if (accessPoints != null && accessPoints.length > 0) {
+ fireScanStateChanged(false);
+ }
}
- public void postUpdateItems() {
- mUiHandler.post(new Runnable() {
- @Override
- public void run() {
- updateItems();
- }
- });
+ @Override
+ public void onDetailItemClick(Item item) {
+ if (item == null || item.tag == null) return;
+ final AccessPoint ap = (AccessPoint) item.tag;
+ if (!ap.isConnected) {
+ mController.connect(ap);
+ }
+ showDetail(false);
+ }
+
+ @Override
+ public void onDetailItemDisconnect(Item item) {
+ // noop
+ }
+
+ public void setItemsVisible(boolean visible) {
+ if (mItems == null) return;
+ mItems.setItemsVisible(visible);
}
private void updateItems() {
if (mItems == null) return;
- mItems.removeAllViews();
- if (mAccessPoints == null || mAccessPoints.length == 0 || !mState.enabled) return;
- for (int i = 0; i < mAccessPoints.length; i++) {
- final AccessPoint ap = mAccessPoints[i];
- if (ap == null) continue;
- final View item = LayoutInflater.from(mContext).inflate(R.layout.qs_detail_item,
- mItems, false);
- final ImageView iv = (ImageView) item.findViewById(android.R.id.icon);
- iv.setImageResource(ap.iconId);
- final TextView title = (TextView) item.findViewById(android.R.id.title);
- title.setText(ap.ssid);
- final TextView summary = (TextView) item.findViewById(android.R.id.summary);
- if (ap.isConnected) {
- item.setMinimumHeight(mContext.getResources()
- .getDimensionPixelSize(R.dimen.qs_detail_item_height_twoline));
- summary.setText(R.string.quick_settings_connected);
- } else {
- summary.setVisibility(View.GONE);
- }
- item.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (!ap.isConnected) {
- mController.connect(ap);
- }
- showDetail(false);
+ Item[] items = null;
+ if (mAccessPoints != null) {
+ items = new Item[mAccessPoints.length];
+ for (int i = 0; i < mAccessPoints.length; i++) {
+ final AccessPoint ap = mAccessPoints[i];
+ final Item item = new Item();
+ item.tag = ap;
+ item.icon = ap.iconId;
+ item.line1 = ap.ssid;
+ if (ap.isConnected) {
+ item.line2 = mContext.getString(R.string.quick_settings_connected);
}
- });
- mItems.addView(item);
- if (mItems.getChildCount() == MAX_ITEMS) break;
+ items[i] = item;
+ }
}
+ mItems.setItems(items);
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java b/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java
index 1cfc892..59f7a80 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/FadedEdgeDrawHelper.java
@@ -142,6 +142,7 @@
mFadeMatrix.setScale(1, fadeHeight * topFadeStrength);
mFadeMatrix.postTranslate(left, top);
mFade.setLocalMatrix(mFadeMatrix);
+ mFadePaint.setShader(mFade);
canvas.drawRect(left, top, right, top + length, mFadePaint);
if (mBlackPaint == null) {
@@ -157,6 +158,7 @@
mFadeMatrix.postRotate(180);
mFadeMatrix.postTranslate(left, bottom);
mFade.setLocalMatrix(mFadeMatrix);
+ mFadePaint.setShader(mFade);
canvas.drawRect(left, bottom - length, right, bottom, mFadePaint);
}
@@ -165,6 +167,7 @@
mFadeMatrix.postRotate(-90);
mFadeMatrix.postTranslate(left, top);
mFade.setLocalMatrix(mFadeMatrix);
+ mFadePaint.setShader(mFade);
canvas.drawRect(left, top, left + length, bottom, mFadePaint);
}
@@ -173,6 +176,7 @@
mFadeMatrix.postRotate(90);
mFadeMatrix.postTranslate(right, top);
mFade.setLocalMatrix(mFadeMatrix);
+ mFadePaint.setShader(mFade);
canvas.drawRect(right - length, top, right, bottom, mFadePaint);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 0c28488..a9a606f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -23,12 +23,10 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Handler;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.view.View;
import com.android.systemui.R;
@@ -70,6 +68,7 @@
// Recents service binding
Handler mHandler;
boolean mBootCompleted = false;
+ boolean mStartAnimationTriggered = false;
// Task launching
RecentsConfiguration mConfig;
@@ -254,6 +253,7 @@
* Creates the activity options for a unknown state->recents transition.
*/
ActivityOptions getUnknownTransitionActivityOptions() {
+ mStartAnimationTriggered = false;
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_unknown_enter,
R.anim.recents_from_unknown_exit, mHandler, this);
@@ -263,6 +263,7 @@
* Creates the activity options for a home->recents transition.
*/
ActivityOptions getHomeTransitionActivityOptions() {
+ mStartAnimationTriggered = false;
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_launcher_enter,
R.anim.recents_from_launcher_exit, mHandler, this);
@@ -281,6 +282,7 @@
// Take the full screenshot
sLastScreenshot = mSystemServicesProxy.takeAppScreenshot();
if (sLastScreenshot != null) {
+ mStartAnimationTriggered = false;
return ActivityOptions.makeCustomAnimation(mContext,
R.anim.recents_from_app_enter,
R.anim.recents_from_app_exit, mHandler, this);
@@ -304,6 +306,7 @@
c.setBitmap(null);
// Recycle the old thumbnail
firstThumbnail.recycle();
+ mStartAnimationTriggered = false;
return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
thumbnail, toTaskRect.left, toTaskRect.top, this);
}
@@ -317,6 +320,11 @@
Rect getThumbnailTransitionRect(int runningTaskId) {
// Get the stack of tasks that we are animating into
TaskStack stack = RecentsTaskLoader.getShallowTaskStack(mSystemServicesProxy);
+ if (stack.getTaskCount() == 0) {
+ return new Rect();
+ }
+
+ // Get the stack
TaskStackView tsv = new TaskStackView(mContext, stack);
TaskStackViewLayoutAlgorithm algo = tsv.getStackAlgorithm();
tsv.computeRects(mTaskStackBounds.width(), mTaskStackBounds.height() - mStatusBarHeight, 0, 0);
@@ -446,9 +454,12 @@
@Override
public void onAnimationStarted() {
// Notify recents to start the enter animation
- Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
- intent.setPackage(mContext.getPackageName());
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mContext.sendBroadcast(intent);
+ if (!mStartAnimationTriggered) {
+ Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
+ intent.setPackage(mContext.getPackageName());
+ intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ mContext.sendBroadcast(intent);
+ mStartAnimationTriggered = true;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index d1efb57..56de0be 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -48,6 +48,7 @@
import com.android.systemui.recents.views.SystemBarScrimViews;
import com.android.systemui.recents.views.ViewAnimation;
+import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
@@ -125,7 +126,7 @@
}
}
- // Broadcast receiver to handle messages from our RecentsService
+ // Broadcast receiver to handle messages from AlternateRecentsComponent
final BroadcastReceiver mServiceBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -458,11 +459,6 @@
filter.addAction(ACTION_START_ENTER_ANIMATION);
registerReceiver(mServiceBroadcastReceiver, filter);
- // Start listening for widget package changes if there is one bound
- if (mConfig.searchBarAppWidgetId >= 0) {
- mAppWidgetHost.startListening(this);
- }
-
mVisible = true;
}
@@ -473,6 +469,22 @@
Console.AnsiRed);
}
super.onResume();
+
+ // Start listening for widget package changes if there is one bound, post it since we don't
+ // want it stalling the startup
+ if (mConfig.searchBarAppWidgetId >= 0) {
+ final WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks> callback =
+ new WeakReference<RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks>(this);
+ mRecentsView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ RecentsAppWidgetHost.RecentsAppWidgetHostCallbacks cb = callback.get();
+ if (cb != null) {
+ mAppWidgetHost.startListening(cb);
+ }
+ }
+ }, 1);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index b039485..e62d989 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -92,6 +92,11 @@
public int taskBarExitAnimDuration;
public int taskBarDismissDozeDelaySeconds;
+ /** Lock to app */
+ public int taskViewLockToAppButtonHeight;
+ public int taskViewLockToAppShortAnimDuration;
+ public int taskViewLockToAppLongAnimDuration;
+
/** Nav bar scrim */
public int navBarScrimEnterDuration;
@@ -226,6 +231,14 @@
taskBarDismissDozeDelaySeconds =
res.getInteger(R.integer.recents_task_bar_dismiss_delay_seconds);
+ // Lock to app
+ taskViewLockToAppButtonHeight =
+ res.getDimensionPixelSize(R.dimen.recents_task_view_lock_to_app_button_height);
+ taskViewLockToAppShortAnimDuration =
+ res.getInteger(R.integer.recents_animate_lock_to_app_button_short_duration);
+ taskViewLockToAppLongAnimDuration =
+ res.getInteger(R.integer.recents_animate_lock_to_app_button_long_duration);
+
// Nav bar scrim
navBarScrimEnterDuration =
res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 05c0f58..b8beda6f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -17,8 +17,10 @@
package com.android.systemui.recents.misc;
import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.AppGlobals;
+import android.app.IActivityManager;
import android.app.SearchManager;
import android.appwidget.AppWidgetHost;
import android.appwidget.AppWidgetManager;
@@ -66,6 +68,7 @@
final static String TAG = "SystemServicesProxy";
ActivityManager mAm;
+ IActivityManager mIam;
AppWidgetManager mAwm;
PackageManager mPm;
IPackageManager mIpm;
@@ -83,6 +86,7 @@
/** Private constructor */
public SystemServicesProxy(Context context) {
mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+ mIam = ActivityManagerNative.getDefault();
mAwm = AppWidgetManager.getInstance(context);
mPm = context.getPackageManager();
mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -407,6 +411,17 @@
}
/**
+ * Locks the current task.
+ */
+ public void lockCurrentTask() {
+ if (mIam == null) return;
+
+ try {
+ mIam.startLockTaskModeOnCurrent();
+ } catch (RemoteException e) {}
+ }
+
+ /**
* Takes a screenshot of the current surface.
*/
public Bitmap takeScreenshot() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 29f1440..854ea1c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -396,7 +396,7 @@
Resources res = context.getResources();
ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
TaskStack stack = new TaskStack();
- SpaceNode root = new SpaceNode(context);
+ SpaceNode root = new SpaceNode();
root.setStack(stack);
// Get the recent tasks
@@ -428,8 +428,9 @@
boolean isForemostTask = (i == (taskCount - 1));
// Create a new task
- Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, activityLabel,
- activityIcon, activityColor, t.userId, t.firstActiveTime, t.lastActiveTime);
+ Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, t.affiliatedTaskId,
+ activityLabel, activityIcon, activityColor, t.userId, t.firstActiveTime,
+ t.lastActiveTime, (i == (taskCount - 1)));
// Preload the specified number of apps
if (i >= (taskCount - preloadCount)) {
@@ -522,8 +523,8 @@
ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
if (info == null) continue;
- stack.addTask(new Task(t.persistentId, true, t.baseIntent, null, null, 0, 0,
- t.firstActiveTime, t.lastActiveTime));
+ stack.addTask(new Task(t.persistentId, true, t.baseIntent, t.affiliatedTaskId, null,
+ null, 0, 0, t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1))));
}
stack.createSimulatedAffiliatedGroupings();
return stack;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
index 20be415..831698a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/SpaceNode.java
@@ -16,7 +16,6 @@
package com.android.systemui.recents.model;
-import android.content.Context;
import android.graphics.Rect;
import java.util.ArrayList;
@@ -35,15 +34,13 @@
public void onSpaceNodeMeasured(SpaceNode node, Rect rect);
}
- Context mContext;
-
SpaceNode mStartNode;
SpaceNode mEndNode;
TaskStack mStack;
- public SpaceNode(Context context) {
- mContext = context;
+ public SpaceNode() {
+ // Do nothing
}
/** Sets the current stack for this space node */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 002395f..88e9f40 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -79,6 +79,7 @@
public TaskKey key;
public TaskGrouping group;
+ public int taskAffiliation;
public Drawable applicationIcon;
public Drawable activityIcon;
public String activityLabel;
@@ -86,6 +87,7 @@
public int colorPrimaryGreyscale;
public Bitmap thumbnail;
public boolean isActive;
+ public boolean canLockToTask;
public int userId;
TaskCallbacks mCb;
@@ -94,15 +96,17 @@
// Only used by RecentsService for task rect calculations.
}
- public Task(int id, boolean isActive, Intent intent, String activityTitle,
- Drawable activityIcon, int colorPrimary, int userId, long firstActiveTime,
- long lastActiveTime) {
+ public Task(int id, boolean isActive, Intent intent, int taskAffiliation, String activityTitle,
+ Drawable activityIcon, int colorPrimary, int userId,
+ long firstActiveTime, long lastActiveTime, boolean canLockToTask) {
this.key = new TaskKey(id, intent, userId, firstActiveTime, lastActiveTime);
+ this.taskAffiliation = taskAffiliation;
this.activityLabel = activityTitle;
this.activityIcon = activityIcon;
this.colorPrimary = colorPrimary;
this.colorPrimaryGreyscale = Utilities.colorToGreyscale(colorPrimary);
this.isActive = isActive;
+ this.canLockToTask = canLockToTask;
this.userId = userId;
}
@@ -148,7 +152,7 @@
public String toString() {
String groupAffiliation = "no group";
if (group != null) {
- groupAffiliation = group.affiliation;
+ groupAffiliation = Integer.toString(group.affiliation);
}
return "Task (" + groupAffiliation + "): " + key.baseIntent.getComponent().getPackageName() +
" [" + super.toString() + "]";
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
index 7793549..9791038 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskGrouping.java
@@ -6,20 +6,21 @@
/** Represents a grouping of tasks witihin a stack. */
public class TaskGrouping {
- String affiliation;
+ int affiliation;
long latestActiveTimeInGroup;
- ArrayList<Task.TaskKey> mTasks = new ArrayList<Task.TaskKey>();
- HashMap<Task.TaskKey, Integer> mTaskIndices = new HashMap<Task.TaskKey, Integer>();
+ Task.TaskKey mFrontMostTaskKey;
+ ArrayList<Task.TaskKey> mTaskKeys = new ArrayList<Task.TaskKey>();
+ HashMap<Task.TaskKey, Integer> mTaskKeyIndices = new HashMap<Task.TaskKey, Integer>();
/** Creates a group with a specified affiliation. */
- public TaskGrouping(String affiliation) {
+ public TaskGrouping(int affiliation) {
this.affiliation = affiliation;
}
/** Adds a new task to this group. */
void addTask(Task t) {
- mTasks.add(t.key);
+ mTaskKeys.add(t.key);
if (t.key.lastActiveTime > latestActiveTimeInGroup) {
latestActiveTimeInGroup = t.key.lastActiveTime;
}
@@ -29,11 +30,11 @@
/** Removes a task from this group. */
void removeTask(Task t) {
- mTasks.remove(t.key);
+ mTaskKeys.remove(t.key);
latestActiveTimeInGroup = 0;
- int taskCount = mTasks.size();
+ int taskCount = mTaskKeys.size();
for (int i = 0; i < taskCount; i++) {
- long lastActiveTime = mTasks.get(i).lastActiveTime;
+ long lastActiveTime = mTaskKeys.get(i).lastActiveTime;
if (lastActiveTime > latestActiveTimeInGroup) {
latestActiveTimeInGroup = lastActiveTime;
}
@@ -44,24 +45,31 @@
/** Gets the front task */
public boolean isFrontMostTask(Task t) {
- return t.key.equals(mTasks.get(mTasks.size() - 1));
+ return (t.key == mFrontMostTaskKey);
}
/** Finds the index of a given task in a group. */
public int indexOf(Task t) {
- return mTaskIndices.get(t.key);
+ return mTaskKeyIndices.get(t.key);
}
/** Returns the number of tasks in this group. */
- public int getTaskCount() { return mTasks.size(); }
+ public int getTaskCount() { return mTaskKeys.size(); }
/** Updates the mapping of tasks to indices. */
private void updateTaskIndices() {
- mTaskIndices.clear();
- int taskCount = mTasks.size();
+ if (mTaskKeys.isEmpty()) {
+ mFrontMostTaskKey = null;
+ mTaskKeyIndices.clear();
+ return;
+ }
+
+ mFrontMostTaskKey = mTaskKeys.get(mTaskKeys.size() - 1);
+ mTaskKeyIndices.clear();
+ int taskCount = mTaskKeys.size();
for (int i = 0; i < taskCount; i++) {
- Task.TaskKey k = mTasks.get(i);
- mTaskIndices.put(k, i);
+ Task.TaskKey k = mTaskKeys.get(i);
+ mTaskKeyIndices.put(k, i);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 6bc74a7..7dd15a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -150,7 +150,7 @@
/* Notifies when a task has been added to the stack */
public void onStackTaskAdded(TaskStack stack, Task t);
/* Notifies when a task has been removed from the stack */
- public void onStackTaskRemoved(TaskStack stack, Task t);
+ public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask);
/** Notifies when the stack was filtered */
public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
/** Notifies when the stack was un-filtered */
@@ -170,11 +170,14 @@
}
}
+ // The task offset to apply to a task id as a group affiliation
+ static final int IndividualTaskIdOffset = 1 << 16;
+
FilteredTaskList mTaskList = new FilteredTaskList();
TaskStackCallbacks mCb;
ArrayList<TaskGrouping> mGroups = new ArrayList<TaskGrouping>();
- HashMap<String, TaskGrouping> mAffinitiesGroups = new HashMap<String, TaskGrouping>();
+ HashMap<Integer, TaskGrouping> mAffinitiesGroups = new HashMap<Integer, TaskGrouping>();
/** Sets the callbacks for this task stack */
public void setCallbacks(TaskStackCallbacks cb) {
@@ -200,9 +203,15 @@
if (group.getTaskCount() == 0) {
removeGroup(group);
}
+ // Update the lock-to-app state
+ Task newFrontMostTask = getFrontMostTask();
+ t.canLockToTask = false;
+ if (newFrontMostTask != null) {
+ newFrontMostTask.canLockToTask = true;
+ }
if (mCb != null) {
// Notify that a task has been removed
- mCb.onStackTaskRemoved(this, t);
+ mCb.onStackTaskRemoved(this, t, newFrontMostTask);
}
}
}
@@ -223,7 +232,7 @@
}
if (mCb != null) {
// Notify that a task has been removed
- mCb.onStackTaskRemoved(this, t);
+ mCb.onStackTaskRemoved(this, t, null);
}
}
mTaskList.set(tasks);
@@ -236,6 +245,7 @@
/** Gets the front task */
public Task getFrontMostTask() {
+ if (mTaskList.size() == 0) return null;
return mTaskList.getTasks().get(mTaskList.size() - 1);
}
@@ -303,7 +313,7 @@
}
/** Returns the group with the specified affiliation. */
- public TaskGrouping getGroupWithAffiliation(String affiliation) {
+ public TaskGrouping getGroupWithAffiliation(int affiliation) {
return mAffinitiesGroups.get(affiliation);
}
@@ -325,9 +335,9 @@
NamedCounter counter = new NamedCounter("task-group", "");
int taskCount = tasks.size();
String prevPackage = "";
- String prevAffiliation = "";
+ int prevAffiliation = -1;
Random r = new Random();
- int groupCountDown = 1000;
+ int groupCountDown = 5;
for (int i = 0; i < taskCount; i++) {
Task t = tasks.get(i);
String packageName = t.key.baseIntent.getComponent().getPackageName();
@@ -337,12 +347,12 @@
group = getGroupWithAffiliation(prevAffiliation);
groupCountDown--;
} else {
- String affiliation = counter.nextName();
+ int affiliation = IndividualTaskIdOffset + t.key.id;
group = new TaskGrouping(affiliation);
addGroup(group);
prevAffiliation = affiliation;
prevPackage = packageName;
- groupCountDown = 1000;
+ groupCountDown = 5;
}
group.addTask(t);
taskMap.put(t.key, t);
@@ -361,13 +371,13 @@
int groupCount = mGroups.size();
for (int i = 0; i < groupCount; i++) {
TaskGrouping group = mGroups.get(i);
- Collections.sort(group.mTasks, new Comparator<Task.TaskKey>() {
+ Collections.sort(group.mTaskKeys, new Comparator<Task.TaskKey>() {
@Override
public int compare(Task.TaskKey taskKey, Task.TaskKey taskKey2) {
return (int) (taskKey.firstActiveTime - taskKey2.firstActiveTime);
}
});
- ArrayList<Task.TaskKey> groupTasks = group.mTasks;
+ ArrayList<Task.TaskKey> groupTasks = group.mTaskKeys;
int groupTaskCount = groupTasks.size();
for (int j = 0; j < groupTaskCount; j++) {
tasks.set(taskIndex, taskMap.get(groupTasks.get(j)));
@@ -376,14 +386,20 @@
}
mTaskList.set(tasks);
} else {
- // Create a group per task
- NamedCounter counter = new NamedCounter("task-group", "");
+ // Create the task groups
ArrayList<Task> tasks = mTaskList.getTasks();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
Task t = tasks.get(i);
- TaskGrouping group = new TaskGrouping(counter.nextName());
- addGroup(group);
+ TaskGrouping group;
+ int affiliation = t.taskAffiliation > 0 ? t.taskAffiliation :
+ IndividualTaskIdOffset + t.key.id;
+ if (mAffinitiesGroups.containsKey(affiliation)) {
+ group = getGroupWithAffiliation(affiliation);
+ } else {
+ group = new TaskGrouping(affiliation);
+ addGroup(group);
+ }
group.addTask(t);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 85afb32..99b012e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -34,9 +34,10 @@
import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
-import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsPackageMonitor;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.SpaceNode;
@@ -144,7 +145,7 @@
Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
"Found focused Task");
}
- onTaskViewClicked(stackView, tv, stack, task);
+ onTaskViewClicked(stackView, tv, stack, task, false);
return true;
}
}
@@ -180,7 +181,7 @@
tv = stv;
}
}
- onTaskViewClicked(stackView, tv, stack, task);
+ onTaskViewClicked(stackView, tv, stack, task, false);
return true;
}
}
@@ -431,7 +432,7 @@
@Override
public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
- final TaskStack stack, final Task task) {
+ final TaskStack stack, final Task task, final boolean lockToTask) {
// Notify any callbacks of the launching of a new task
if (mCb != null) {
mCb.onTaskViewClicked();
@@ -456,6 +457,8 @@
}
// Compute the thumbnail to scale up from
+ final SystemServicesProxy ssp =
+ RecentsTaskLoader.getInstance().getSystemServicesProxy();
ActivityOptions opts = null;
int thumbnailWidth = transform.rect.width();
int thumbnailHeight = transform.rect.height();
@@ -469,8 +472,26 @@
new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
c.setBitmap(null);
+ ActivityOptions.OnAnimationStartedListener animStartedListener = null;
+ if (lockToTask) {
+ animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+ boolean mTriggered = false;
+ @Override
+ public void onAnimationStarted() {
+ if (!mTriggered) {
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ ssp.lockCurrentTask();
+ }
+ }, 350);
+ mTriggered = true;
+ }
+ }
+ };
+ }
opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
- b, offsetX, offsetY);
+ b, offsetX, offsetY, animStartedListener);
}
final ActivityOptions launchOpts = opts;
@@ -496,8 +517,12 @@
UserHandle taskUser = new UserHandle(task.userId);
if (launchOpts != null) {
getContext().startActivityAsUser(i, launchOpts.toBundle(), taskUser);
+
} else {
getContext().startActivityAsUser(i, taskUser);
+ if (lockToTask) {
+ ssp.lockCurrentTask();
+ }
}
} catch (ActivityNotFoundException anfe) {
Console.logError(getContext(), "Could not start Activity");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index f38c637..599c590 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -51,11 +51,12 @@
/* The visual representation of a task stack view */
public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
- View.OnClickListener, RecentsPackageMonitor.PackageCallbacks {
+ RecentsPackageMonitor.PackageCallbacks {
/** The TaskView callbacks */
interface TaskStackViewCallbacks {
- public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
+ public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
+ boolean lockToTask);
public void onTaskViewAppInfoClicked(Task t);
public void onTaskViewDismissed(Task t);
public void onAllTaskViewsDismissed();
@@ -734,7 +735,8 @@
for (int i = 0; i < childCount; i++) {
TaskView t = (TaskView) getChildAt(i);
t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height(), MeasureSpec.EXACTLY));
+ MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
+ mConfig.taskViewLockToAppButtonHeight, MeasureSpec.EXACTLY));
}
setMeasuredDimension(width, height);
@@ -766,21 +768,36 @@
TaskView t = (TaskView) getChildAt(i);
t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
- mStackAlgorithm.mTaskRect.height());
+ mStackAlgorithm.mTaskRect.height() + mConfig.taskViewLockToAppButtonHeight);
}
if (mAwaitingFirstLayout) {
// Mark that we have completely the first layout
mAwaitingFirstLayout = false;
+ // Find the target task with the specified id
+ ArrayList<Task> tasks = mStack.getTasks();
+ Task targetTask = null;
+ int targetTaskId = mConfig.launchedToTaskId;
+ if (targetTaskId != -1) {
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task t = tasks.get(i);
+ if (t.key.id == targetTaskId) {
+ targetTask = t;
+ break;
+ }
+ }
+ }
+
// Prepare the first view for its enter animation
int offsetTopAlign = -mStackAlgorithm.mTaskRect.top;
int offscreenY = mStackAlgorithm.mRect.bottom -
(mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mRect.top);
for (int i = childCount - 1; i >= 0; i--) {
TaskView tv = (TaskView) getChildAt(i);
- tv.prepareEnterRecentsAnimation((i == (getChildCount() - 1)), offsetTopAlign,
- offscreenY, mStackAlgorithm.mTaskRect);
+ tv.prepareEnterRecentsAnimation(tv.getTask() == targetTask, offsetTopAlign,
+ offscreenY);
}
// If the enter animation started already and we haven't completed a layout yet, do the
@@ -809,41 +826,40 @@
}
if (mStack.getTaskCount() > 0) {
- if (Constants.DebugFlags.App.EnableScreenshotAppTransition) {
- // Find the target task with the specified id
- ArrayList<Task> tasks = mStack.getTasks();
- Task targetTask = null;
- int targetTaskId = mConfig.launchedToTaskId;
- if (targetTaskId != -1) {
- int taskCount = tasks.size();
- for (int i = 0; i < taskCount; i++) {
- Task t = tasks.get(i);
- if (t.key.id == targetTaskId) {
- targetTask = t;
- break;
- }
+ // Find the target task with the specified id
+ ArrayList<Task> tasks = mStack.getTasks();
+ Task targetTask = null;
+ int targetTaskId = mConfig.launchedToTaskId;
+ if (targetTaskId != -1) {
+ int taskCount = tasks.size();
+ for (int i = 0; i < taskCount; i++) {
+ Task t = tasks.get(i);
+ if (t.key.id == targetTaskId) {
+ targetTask = t;
+ break;
}
}
+ }
- // Find the group and task index of the target task
- if (targetTask != null) {
- ctx.targetTaskTransform = new TaskViewTransform();
- mStackAlgorithm.getStackTransform(targetTask, getStackScroll(), ctx.targetTaskTransform);
- Rect taskStackBounds = new Rect();
- mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
- ctx.targetTaskTransform.rect.offset(taskStackBounds.left, taskStackBounds.top);
- }
+ // Find the transform for the target task
+ if (targetTask != null) {
+ ctx.targetTaskTransform = new TaskViewTransform();
+ mStackAlgorithm.getStackTransform(targetTask, getStackScroll(), ctx.targetTaskTransform);
+ Rect taskStackBounds = new Rect();
+ mConfig.getTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), taskStackBounds);
+ ctx.targetTaskTransform.rect.offset(taskStackBounds.left, taskStackBounds.top);
}
// Animate all the task views into view
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
TaskView tv = (TaskView) getChildAt(i);
+ Task task = tv.getTask();
ctx.currentTaskTransform = new TaskViewTransform();
ctx.currentStackViewIndex = i;
ctx.currentStackViewCount = childCount;
- ctx.isCurrentTaskFrontMost = (i == (getChildCount() - 1));
- mStackAlgorithm.getStackTransform(tv.getTask(), getStackScroll(), ctx.currentTaskTransform);
+ ctx.isCurrentTaskLaunchTarget = (task == targetTask);
+ mStackAlgorithm.getStackTransform(task, getStackScroll(), ctx.currentTaskTransform);
tv.startEnterRecentsAnimation(ctx);
}
@@ -889,11 +905,6 @@
mUIDozeTrigger.poke();
}
- /** Disables handling touch on this task view. */
- void setTouchOnTaskView(TaskView tv, boolean enabled) {
- tv.setOnClickListener(enabled ? this : null);
- }
-
/**** TaskStackCallbacks Implementation ****/
@Override
@@ -905,25 +916,33 @@
}
@Override
- public void onStackTaskRemoved(TaskStack stack, Task t) {
+ public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
// Update the task offsets
mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
// Remove the view associated with this task, we can't rely on updateTransforms
// to work here because the task is no longer in the list
- TaskView tv = getChildViewForTask(t);
+ TaskView tv = getChildViewForTask(removedTask);
if (tv != null) {
mViewPool.returnViewToPool(tv);
}
// Notify the callback that we've removed the task and it can clean up after it
- mCb.onTaskViewDismissed(t);
+ mCb.onTaskViewDismissed(removedTask);
// Update the min/max scroll and animate other task views into their new positions
updateMinMaxScroll(true);
int movement = (int) mStackAlgorithm.getTaskOverlapHeight();
requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
+ // Update the new front most task
+ if (newFrontMostTask != null) {
+ TaskView frontTv = getChildViewForTask(newFrontMostTask);
+ if (frontTv != null) {
+ frontTv.onTaskBound(newFrontMostTask);
+ }
+ }
+
// If there are no remaining tasks, then either unfilter the current stack, or just close
// the activity if there are no filtered stacks
if (mStack.getTaskCount() == 0) {
@@ -1072,7 +1091,7 @@
addView(tv, insertIndex);
// Set the callbacks and listeners for this new view
- setTouchOnTaskView(tv, true);
+ tv.setTouchEnabled(true);
tv.setCallbacks(this);
} else {
attachViewToParent(tv, insertIndex, tv.getLayoutParams());
@@ -1115,18 +1134,7 @@
}
@Override
- public void onTaskViewDismissed(TaskView tv) {
- Task task = tv.getTask();
- // Remove the task from the view
- mStack.removeTask(task);
- }
-
- /**** View.OnClickListener Implementation ****/
-
- @Override
- public void onClick(View v) {
- TaskView tv = (TaskView) v;
- Task task = tv.getTask();
+ public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
if (Console.Enabled) {
Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
task + " cb: " + mCb);
@@ -1136,10 +1144,17 @@
mUIDozeTrigger.stopDozing();
if (mCb != null) {
- mCb.onTaskViewClicked(this, tv, mStack, task);
+ mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask);
}
}
+ @Override
+ public void onTaskViewDismissed(TaskView tv) {
+ Task task = tv.getTask();
+ // Remove the task from the view
+ mStack.removeTask(task);
+ }
+
/**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 789b4f7..908e063 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -95,9 +95,11 @@
if (numTasks <= 1) {
// If there is only one task, then center the task in the stack rect (sans peek)
- mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
+ mMinScroll = mMaxScroll = -(stackHeight -
+ (taskHeight + mConfig.taskViewLockToAppButtonHeight)) / 2;
} else {
- int maxScrollHeight = taskHeight + getStackScrollForTaskIndex(tasks.get(tasks.size() - 1));
+ int maxScrollHeight = getStackScrollForTaskIndex(tasks.get(tasks.size() - 1))
+ + taskHeight + mConfig.taskViewLockToAppButtonHeight;
mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
mMaxScroll = maxScrollHeight - stackHeight;
}
@@ -133,7 +135,7 @@
// Set the y translation
if (boundedT < 0f) {
transformOut.translationY = (int) ((Math.max(-numPeekCards, boundedT) /
- numPeekCards) * peekHeight - scaleYOffset);
+ numPeekCards) * peekHeight - scaleYOffset - scaleBarYOffset);
} else {
transformOut.translationY = (int) (boundedT * overlapHeight - scaleYOffset - scaleBarYOffset);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index e186e2e..15ace13 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -377,7 +377,9 @@
// Enable HW layers on that task
tv.enableHwLayers();
// Disallow touch events from this task view
- mSv.setTouchOnTaskView(tv, false);
+ tv.setTouchEnabled(false);
+ // Hide the footer
+ tv.animateFooterVisibility(false, mSv.mConfig.taskViewLockToAppShortAnimDuration, 0);
// Disallow parents from intercepting touch events
final ViewParent parent = mSv.getParent();
if (parent != null) {
@@ -413,7 +415,9 @@
// Re-enable clipping with the stack
tv.setClipViewInStack(true);
// Re-enable touch events from this task view
- mSv.setTouchOnTaskView(tv, true);
+ tv.setTouchEnabled(true);
+ // Restore the footer
+ tv.animateFooterVisibility(true, mSv.mConfig.taskViewLockToAppShortAnimDuration, 0);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 1ba0560..5524e15 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -19,7 +19,6 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -28,6 +27,7 @@
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
+import android.view.ViewOutlineProvider;
import android.view.ViewPropertyAnimator;
import android.view.animation.AccelerateInterpolator;
import android.widget.FrameLayout;
@@ -36,6 +36,7 @@
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
/* A task view */
@@ -45,11 +46,16 @@
interface TaskViewCallbacks {
public void onTaskViewAppIconClicked(TaskView tv);
public void onTaskViewAppInfoClicked(TaskView tv);
+ public void onTaskViewClicked(TaskView tv, Task t, boolean lockToTask);
public void onTaskViewDismissed(TaskView tv);
}
RecentsConfiguration mConfig;
+ int mFooterHeight;
+ int mMaxFooterHeight;
+ ObjectAnimator mFooterAnimator;
+
int mDim;
int mMaxDim;
AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
@@ -64,6 +70,7 @@
TaskThumbnailView mThumbnailView;
TaskBarView mBarView;
+ View mLockToAppButtonView;
TaskViewCallbacks mCb;
// Optimizations
@@ -103,9 +110,20 @@
public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mConfig = RecentsConfiguration.getInstance();
+ mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
setWillNotDraw(false);
setClipToOutline(true);
setDim(getDim());
+ setFooterHeight(getFooterHeight());
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public boolean getOutline(View view, Outline outline) {
+ int height = getHeight() - mMaxFooterHeight + mFooterHeight;
+ outline.setRoundRect(0, 0, getWidth(), height,
+ mConfig.taskViewRoundedCornerRadiusPx);
+ return true;
+ }
+ });
}
@Override
@@ -118,6 +136,7 @@
// Bind the views
mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
+ mLockToAppButtonView = findViewById(R.id.lock_to_app);
if (mTaskDataLoaded) {
onTaskDataLoaded();
@@ -126,13 +145,19 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
- // Update the outline
- Outline o = new Outline();
- o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
- mConfig.taskViewRoundedCornerRadiusPx);
- setOutline(o);
+ // Measure the bar view, thumbnail, and lock-to-app buttons
+ mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
+ mLockToAppButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
+ MeasureSpec.EXACTLY));
+ // Measure the thumbnail height to be the same as the width
+ mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
+ setMeasuredDimension(width, height);
}
/** Set callback */
@@ -242,10 +267,10 @@
/** Prepares this task view for the enter-recents animations. This is called earlier in the
* first layout because the actual animation into recents may take a long time. */
- public void prepareEnterRecentsAnimation(boolean isTaskViewFrontMost, int offsetY, int offscreenY,
- Rect taskRect) {
+ public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, int offsetY,
+ int offscreenY) {
if (mConfig.launchedFromAppWithScreenshot) {
- if (isTaskViewFrontMost) {
+ if (isTaskViewLaunchTargetTask) {
// Hide the task view as we are going to animate the full screenshot into view
// and then replace it with this view once we are done
setVisibility(View.INVISIBLE);
@@ -259,7 +284,7 @@
}
} else if (mConfig.launchedFromAppWithThumbnail) {
- if (isTaskViewFrontMost) {
+ if (isTaskViewLaunchTargetTask) {
// Hide the front most task bar view so we can animate it in
mBarView.prepareEnterRecentsAnimation();
// Set the dim to 0 so we can animate it in
@@ -282,7 +307,7 @@
TaskViewTransform transform = ctx.currentTaskTransform;
if (mConfig.launchedFromAppWithScreenshot) {
- if (ctx.isCurrentTaskFrontMost) {
+ if (ctx.isCurrentTaskLaunchTarget) {
// Animate the full screenshot down first, before swapping with this task view
ctx.fullScreenshotView.animateOnEnterRecents(ctx, new Runnable() {
@Override
@@ -290,6 +315,8 @@
// Animate the task bar of the first task view
mBarView.startEnterRecentsAnimation(0, mEnableThumbnailClip);
setVisibility(View.VISIBLE);
+ // Animate the footer into view
+ animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration, 0);
// Decrement the post animation trigger
ctx.postAnimationTrigger.decrement();
}
@@ -318,7 +345,7 @@
ctx.postAnimationTrigger.increment();
} else if (mConfig.launchedFromAppWithThumbnail) {
- if (ctx.isCurrentTaskFrontMost) {
+ if (ctx.isCurrentTaskLaunchTarget) {
// Animate the task bar of the first task view
mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, mEnableThumbnailClip);
@@ -336,6 +363,10 @@
});
anim.start();
ctx.postAnimationTrigger.increment();
+
+ // Animate the footer into view
+ animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration,
+ mConfig.taskBarEnterAnimDelay);
} else {
mEnableThumbnailClip.run();
}
@@ -367,9 +398,16 @@
})
.start();
ctx.postAnimationTrigger.increment();
+
+ // Animate the footer into view
+ animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration,
+ mConfig.taskBarEnterAnimDelay);
} else {
// Otherwise, just enable the thumbnail clip
mEnableThumbnailClip.run();
+
+ // Animate the footer into view
+ animateFooterVisibility(true, 0, 0);
}
}
@@ -458,12 +496,14 @@
void enableHwLayers() {
mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
mBarView.enableHwLayers();
+ mLockToAppButtonView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
}
/** Disable the hw layers on this task view */
void disableHwLayers() {
mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
mBarView.disableHwLayers();
+ mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
}
/** Sets the stubbed state of this task view. */
@@ -500,6 +540,57 @@
}
}
+ /** Sets the footer height. */
+ public void setFooterHeight(int height) {
+ mFooterHeight = height;
+ invalidateOutline();
+ invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
+ getMeasuredHeight());
+ }
+
+ /** Gets the footer height. */
+ public int getFooterHeight() {
+ return mFooterHeight;
+ }
+
+ /** Animates the footer into and out of view. */
+ public void animateFooterVisibility(boolean visible, int duration, int delay) {
+ if (!mTask.canLockToTask) return;
+ if (mMaxFooterHeight <= 0) return;
+
+ if (mFooterAnimator != null) {
+ mFooterAnimator.removeAllListeners();
+ mFooterAnimator.cancel();
+ }
+ int height = visible ? mMaxFooterHeight : 0;
+ if (visible && mLockToAppButtonView.getVisibility() != View.VISIBLE) {
+ if (duration > 0) {
+ setFooterHeight(0);
+ } else {
+ setFooterHeight(mMaxFooterHeight);
+ }
+ mLockToAppButtonView.setVisibility(View.VISIBLE);
+ }
+ if (duration > 0) {
+ mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", height);
+ mFooterAnimator.setDuration(duration);
+ mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
+ if (!visible) {
+ mFooterAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLockToAppButtonView.setVisibility(View.INVISIBLE);
+ }
+ });
+ }
+ mFooterAnimator.start();
+ } else {
+ if (!visible) {
+ mLockToAppButtonView.setVisibility(View.INVISIBLE);
+ }
+ }
+ }
+
/** Returns the current dim. */
public void setDim(int dim) {
mDim = dim;
@@ -585,6 +676,11 @@
public void onTaskBound(Task t) {
mTask = t;
mTask.setCallbacks(this);
+ if (getMeasuredWidth() == 0) {
+ animateFooterVisibility(t.canLockToTask, 0, 0);
+ } else {
+ animateFooterVisibility(t.canLockToTask, mConfig.taskViewLockToAppLongAnimDuration, 0);
+ }
}
@Override
@@ -598,6 +694,7 @@
mBarView.mApplicationIcon.setOnClickListener(this);
}
mBarView.mDismissButton.setOnClickListener(this);
+ mLockToAppButtonView.setOnClickListener(this);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
if (mConfig.developerOptionsEnabled) {
mBarView.mApplicationIcon.setOnLongClickListener(this);
@@ -619,6 +716,7 @@
mBarView.mApplicationIcon.setOnClickListener(null);
}
mBarView.mDismissButton.setOnClickListener(null);
+ mLockToAppButtonView.setOnClickListener(null);
if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
mBarView.mApplicationIcon.setOnLongClickListener(null);
}
@@ -626,6 +724,11 @@
mTaskDataLoaded = false;
}
+ /** Enables/disables handling touch on this task view. */
+ void setTouchEnabled(boolean enabled) {
+ setOnClickListener(enabled ? this : null);
+ }
+
@Override
public void onClick(final View v) {
// We purposely post the handler delayed to allow for the touch feedback to draw
@@ -643,6 +746,10 @@
mCb.onTaskViewDismissed(tv);
}
});
+ // Hide the footer
+ tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration, 0);
+ } else if (v == tv || v == mLockToAppButtonView) {
+ mCb.onTaskViewClicked(tv, tv.getTask(), (v == mLockToAppButtonView));
}
}
}, 125);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
index a531583..e50a5cf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewAnimation.java
@@ -36,7 +36,7 @@
// The transform of the current task view
TaskViewTransform currentTaskTransform;
// Whether this is the front most task view
- boolean isCurrentTaskFrontMost;
+ boolean isCurrentTaskLaunchTarget;
// The view index of the current task view
int currentStackViewIndex;
// The total number of task views
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index 843db04..d9719ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -18,39 +18,50 @@
import android.content.Context;
import android.graphics.Outline;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
/**
* Like {@link ExpandableView}, but setting an outline for the height and clipping.
*/
public abstract class ExpandableOutlineView extends ExpandableView {
- private final Outline mOutline = new Outline();
+ private final Rect mOutlineRect = new Rect();
private boolean mCustomOutline;
private float mDensity;
public ExpandableOutlineView(Context context, AttributeSet attrs) {
super(context, attrs);
mDensity = getResources().getDisplayMetrics().density;
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public boolean getOutline(View view, Outline outline) {
+ if (!mCustomOutline) {
+ outline.setRect(0,
+ mClipTopAmount,
+ getWidth(),
+ Math.max(mActualHeight, mClipTopAmount));
+ } else {
+ outline.setRect(mOutlineRect);
+ }
+ return true;
+ }
+ });
}
@Override
public void setActualHeight(int actualHeight, boolean notifyListeners) {
super.setActualHeight(actualHeight, notifyListeners);
- updateOutline();
+ invalidateOutline();
}
@Override
public void setClipTopAmount(int clipTopAmount) {
super.setClipTopAmount(clipTopAmount);
- updateOutline();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- updateOutline();
+ invalidateOutline();
}
protected void setOutlineRect(RectF rect) {
@@ -58,32 +69,20 @@
setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
} else {
mCustomOutline = false;
- updateOutline();
+ invalidateOutline();
}
}
protected void setOutlineRect(float left, float top, float right, float bottom) {
mCustomOutline = true;
- int rectLeft = (int) left;
- int rectTop = (int) top;
- int rectRight = (int) right;
- int rectBottom = (int) bottom;
+ mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom);
// Outlines need to be at least 1 dp
- rectBottom = (int) Math.max(top + mDensity, rectBottom);
- rectRight = (int) Math.max(left + mDensity, rectRight);
- mOutline.setRect(rectLeft, rectTop, rectRight, rectBottom);
- setOutline(mOutline);
+ mOutlineRect.bottom = (int) Math.max(top + mDensity, mOutlineRect.bottom);
+ mOutlineRect.right = (int) Math.max(left + mDensity, mOutlineRect.right);
+
+ invalidateOutline();
}
- private void updateOutline() {
- if (!mCustomOutline) {
- mOutline.setRect(0,
- mClipTopAmount,
- getWidth(),
- Math.max(mActualHeight, mClipTopAmount));
- setOutline(mOutline);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
index 650abaa..dfeadc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SpeedBumpView.java
@@ -61,7 +61,7 @@
super.onLayout(changed, left, top, right, bottom);
mLine.setPivotX(mLine.getWidth() / 2);
mLine.setPivotY(mLine.getHeight() / 2);
- setOutline(null);
+ setOutlineProvider(null);
}
@Override
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 c962dde..7fb5693 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -639,7 +639,6 @@
}
public void collapse() {
- // TODO: abort animation or ongoing touch
if (DEBUG) logf("collapse: " + this);
if (mPeekPending || mPeekAnimator != null) {
mCollapseAfterPeek = true;
@@ -650,7 +649,7 @@
removeCallbacks(mPeekRunnable);
mPeekRunnable.run();
}
- } else if (!isFullyCollapsed()) {
+ } else if (!isFullyCollapsed() && !mTracking) {
if (mHeightAnimator != null) {
mHeightAnimator.cancel();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 04f9c72..9d7d933 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -2316,7 +2316,12 @@
mGestureRec.dump(fd, pw, args);
}
- mNetworkController.dump(fd, pw, args);
+ if (mNetworkController != null) {
+ mNetworkController.dump(fd, pw, args);
+ }
+ if (mBluetoothController != null) {
+ mBluetoothController.dump(fd, pw, args);
+ }
}
private String hunStateToString(Entry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 0fbdeeb4..33d1b15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -24,6 +24,7 @@
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
@@ -51,7 +52,8 @@
private ViewGroup mSystemIconsContainer;
private View mSystemIconsSuperContainer;
private View mDateTime;
- private TextView mClock;
+ private View mTime;
+ private View mAmPm;
private View mKeyguardCarrierText;
private MultiUserSwitch mMultiUserSwitch;
private ImageView mMultiUserAvatar;
@@ -61,6 +63,8 @@
private View mSignalCluster;
private View mSettingsButton;
private View mQsDetailHeader;
+ private TextView mQsDetailHeaderTitle;
+ private Switch mQsDetailHeaderSwitch;
private View mEmergencyCallsOnly;
private TextView mBatteryLevel;
@@ -95,7 +99,6 @@
private QSPanel mQSPanel;
private final Rect mClipBounds = new Rect();
- private final Outline mOutline = new Outline();
public StatusBarHeaderView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -108,7 +111,8 @@
mSystemIconsContainer = (ViewGroup) findViewById(R.id.system_icons_container);
mSystemIconsSuperContainer.setOnClickListener(this);
mDateTime = findViewById(R.id.datetime);
- mClock = (TextView) findViewById(R.id.clock);
+ mTime = findViewById(R.id.time_view);
+ mAmPm = findViewById(R.id.am_pm_view);
mKeyguardCarrierText = findViewById(R.id.keyguard_carrier_text);
mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch);
mMultiUserAvatar = (ImageView) findViewById(R.id.multi_user_avatar);
@@ -118,6 +122,8 @@
mSettingsButton.setOnClickListener(this);
mQsDetailHeader = findViewById(R.id.qs_detail_header);
mQsDetailHeader.setAlpha(0);
+ mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
+ mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
mEmergencyCallsOnly = findViewById(R.id.header_emergency_calls_only);
mBatteryLevel = (TextView) findViewById(R.id.battery_level);
loadDimens();
@@ -132,8 +138,19 @@
// width changed, update clipping
setClipping(getHeight());
}
- mClock.setPivotX(0);
- mClock.setPivotY(mClock.getBaseline());
+ boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ mTime.setPivotX(rtl ? mTime.getWidth() : 0);
+ mTime.setPivotY(mTime.getBaseline());
+ mAmPm.setPivotX(rtl ? mAmPm.getWidth() : 0);
+ mAmPm.setPivotY(mAmPm.getBaseline());
+ updateAmPmTranslation();
+ }
+ });
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public boolean getOutline(View view, Outline outline) {
+ outline.setRect(mClipBounds);
+ return true;
}
});
}
@@ -164,8 +181,8 @@
getResources().getDimensionPixelSize(R.dimen.multi_user_avatar_collapsed_size)
/ (float) mMultiUserAvatar.getLayoutParams().width;
mClockCollapsedScaleFactor =
- getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size)
- / mClock.getTextSize();
+ (float) getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size)
+ / (float) getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size);
mBatteryPaddingEnd =
getResources().getDimensionPixelSize(R.dimen.battery_level_padding_end);
}
@@ -315,13 +332,21 @@
}
private void updateClockScale() {
+ mAmPm.setScaleX(mClockCollapsedScaleFactor);
+ mAmPm.setScaleY(mClockCollapsedScaleFactor);
if (!mExpanded || mOverscrolled) {
- mClock.setScaleX(mClockCollapsedScaleFactor);
- mClock.setScaleY(mClockCollapsedScaleFactor);
+ mTime.setScaleX(mClockCollapsedScaleFactor);
+ mTime.setScaleY(mClockCollapsedScaleFactor);
} else {
- mClock.setScaleX(1f);
- mClock.setScaleY(1f);
+ mTime.setScaleX(1f);
+ mTime.setScaleY(1f);
}
+ updateAmPmTranslation();
+ }
+
+ private void updateAmPmTranslation() {
+ boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ mAmPm.setTranslationX((rtl ? 1 : -1) * mTime.getWidth() * (1 - mTime.getScaleX()));
}
private void updateBatteryLevelPaddingEnd() {
@@ -409,8 +434,7 @@
private void setClipping(float height) {
mClipBounds.set(getPaddingLeft(), 0, getWidth() - getPaddingRight(), (int) height);
setClipBounds(mClipBounds);
- mOutline.setRect(mClipBounds);
- setOutline(mOutline);
+ invalidateOutline();
}
public void attachSystemIcons(LinearLayout systemIcons) {
@@ -530,10 +554,22 @@
});
}
+ @Override
+ public void onScanStateChanged(final boolean state) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ handleScanStateChanged(state);
+ }
+ });
+ }
+
private void handleToggleStateChanged(boolean state) {
- final Switch headerSwitch = (Switch)
- mQsDetailHeader.findViewById(android.R.id.toggle);
- headerSwitch.setChecked(state);
+ mQsDetailHeaderSwitch.setChecked(state);
+ }
+
+ private void handleScanStateChanged(boolean state) {
+ // TODO - waiting on framework asset
}
private void handleShowingDetail(final QSTile.DetailAdapter detail) {
@@ -541,18 +577,14 @@
transition(mDateTime, !showingDetail);
transition(mQsDetailHeader, showingDetail);
if (showingDetail) {
- final TextView headerTitle = (TextView)
- mQsDetailHeader.findViewById(android.R.id.title);
- headerTitle.setText(detail.getTitle());
- final Switch headerSwitch = (Switch)
- mQsDetailHeader.findViewById(android.R.id.toggle);
+ mQsDetailHeaderTitle.setText(detail.getTitle());
final Boolean toggleState = detail.getToggleState();
if (toggleState == null) {
- headerSwitch.setVisibility(INVISIBLE);
+ mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
mQsDetailHeader.setClickable(false);
} else {
- headerSwitch.setVisibility(VISIBLE);
- headerSwitch.setChecked(toggleState);
+ mQsDetailHeaderSwitch.setVisibility(VISIBLE);
+ mQsDetailHeaderSwitch.setChecked(toggleState);
mQsDetailHeader.setClickable(true);
mQsDetailHeader.setOnClickListener(new OnClickListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
index 8e9fb30..cbdd138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import java.util.Set;
+
public interface BluetoothController {
void addStateChangedCallback(Callback callback);
void removeStateChangedCallback(Callback callback);
@@ -26,8 +28,32 @@
boolean isBluetoothConnecting();
String getLastDeviceName();
void setBluetoothEnabled(boolean enabled);
+ Set<PairedDevice> getPairedDevices();
+ void connect(PairedDevice device);
+ void disconnect(PairedDevice device);
public interface Callback {
void onBluetoothStateChange(boolean enabled, boolean connecting);
+ void onBluetoothPairedDevicesChanged();
+ }
+
+ public static final class PairedDevice {
+ public static int STATE_DISCONNECTED = 0;
+ public static int STATE_CONNECTING = 1;
+ public static int STATE_CONNECTED = 2;
+ public static int STATE_DISCONNECTING = 3;
+
+ public String id;
+ public String name;
+ public int state = STATE_DISCONNECTED;
+ public Object tag;
+
+ public static String stateToString(int state) {
+ if (state == STATE_DISCONNECTED) return "STATE_DISCONNECTED";
+ if (state == STATE_CONNECTING) return "STATE_CONNECTING";
+ if (state == STATE_CONNECTED) return "STATE_CONNECTED";
+ if (state == STATE_DISCONNECTING) return "STATE_DISCONNECTING";
+ return "UNKNOWN";
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 379b509..f021623 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -16,49 +16,91 @@
package com.android.systemui.statusbar.policy;
+import static android.bluetooth.BluetoothAdapter.ERROR;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.profileStateToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString;
+import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.ParcelUuid;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import com.android.systemui.statusbar.policy.BluetoothUtil.Profile;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.Set;
-public class BluetoothControllerImpl extends BroadcastReceiver implements BluetoothController {
- private static final String TAG = "StatusBar.BluetoothController";
+public class BluetoothControllerImpl implements BluetoothController {
+ private static final String TAG = "BluetoothController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private final Context mContext;
private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
- private final Set<BluetoothDevice> mBondedDevices = new HashSet<BluetoothDevice>();
private final BluetoothAdapter mAdapter;
+ private final Receiver mReceiver = new Receiver();
+ private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>();
private boolean mEnabled;
private boolean mConnecting;
private BluetoothDevice mLastDevice;
public BluetoothControllerImpl(Context context) {
- mAdapter = BluetoothAdapter.getDefaultAdapter();
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
- context.registerReceiver(this, filter);
-
- final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- handleAdapterStateChange(adapter.getState());
+ mContext = context;
+ final BluetoothManager bluetoothManager =
+ (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
+ mAdapter = bluetoothManager.getAdapter();
+ if (mAdapter == null) {
+ Log.w(TAG, "Default BT adapter not found");
+ return;
}
- fireCallbacks();
+
+ mReceiver.register();
+ setAdapterState(mAdapter.getState());
updateBondedBluetoothDevices();
}
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("BluetoothController state:");
+ pw.print(" mAdapter="); pw.println(mAdapter);
+ pw.print(" mEnabled="); pw.println(mEnabled);
+ pw.print(" mConnecting="); pw.println(mConnecting);
+ pw.print(" mLastDevice="); pw.println(mLastDevice);
+ pw.print(" mCallbacks.size="); pw.println(mCallbacks.size());
+ pw.print(" mDeviceInfo.size="); pw.println(mDeviceInfo.size());
+ for (int i = 0; i < mDeviceInfo.size(); i++) {
+ final BluetoothDevice device = mDeviceInfo.keyAt(i);
+ final DeviceInfo info = mDeviceInfo.valueAt(i);
+ pw.print(" "); pw.print(deviceToString(device));
+ pw.print('('); pw.print(uuidsToString(device)); pw.print(')');
+ pw.print(" "); pw.println(infoToString(info));
+ }
+ }
+
+ private static String infoToString(DeviceInfo info) {
+ return info == null ? null : ("connectionState=" +
+ connectionStateToString(info.connectionState) + ",bonded=" + info.bonded);
+ }
+
public void addStateChangedCallback(Callback cb) {
mCallbacks.add(cb);
- fireCallback(cb);
+ fireStateChange(cb);
}
@Override
@@ -99,64 +141,191 @@
return mAdapter != null;
}
- public Set<BluetoothDevice> getBondedBluetoothDevices() {
- return mBondedDevices;
+ @Override
+ public ArraySet<PairedDevice> getPairedDevices() {
+ final ArraySet<PairedDevice> rt = new ArraySet<>();
+ for (int i = 0; i < mDeviceInfo.size(); i++) {
+ final BluetoothDevice device = mDeviceInfo.keyAt(i);
+ final DeviceInfo info = mDeviceInfo.valueAt(i);
+ if (!info.bonded) continue;
+ final PairedDevice paired = new PairedDevice();
+ paired.id = device.getAddress();
+ paired.tag = device;
+ paired.name = device.getAliasName();
+ paired.state = connectionStateToPairedDeviceState(info.connectionState);
+ rt.add(paired);
+ }
+ return rt;
+ }
+
+ private static int connectionStateToPairedDeviceState(int state) {
+ if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED;
+ if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING;
+ if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING;
+ return PairedDevice.STATE_DISCONNECTED;
+ }
+
+ @Override
+ public void connect(final PairedDevice pd) {
+ connect(pd, true);
+ }
+
+ @Override
+ public void disconnect(PairedDevice pd) {
+ connect(pd, false);
+ }
+
+ private void connect(PairedDevice pd, final boolean connect) {
+ if (mAdapter == null || pd == null || pd.tag == null) return;
+ final BluetoothDevice device = (BluetoothDevice) pd.tag;
+ final String action = connect ? "connect" : "disconnect";
+ if (DEBUG) Log.d(TAG, action + " " + deviceToString(device));
+ final SparseBooleanArray profiles = new SparseBooleanArray();
+ for (ParcelUuid uuid : device.getUuids()) {
+ final int profile = uuidToProfile(uuid);
+ if (profile == 0) {
+ Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: "
+ + uuidToString(uuid));
+ continue;
+ }
+ final int profileState = mAdapter.getProfileConnectionState(profile);
+ if (DEBUG && !profiles.get(profile)) Log.d(TAG, "Profile " + profileToString(profile)
+ + " state = " + profileStateToString(profileState));
+ final boolean connected = profileState == BluetoothProfile.STATE_CONNECTED;
+ if (connect != connected) {
+ profiles.put(profile, true);
+ }
+ }
+ for (int i = 0; i < profiles.size(); i++) {
+ final int profile = profiles.keyAt(i);
+ mAdapter.getProfileProxy(mContext, new ServiceListener() {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DEBUG) Log.d(TAG, "onServiceConnected " + profileToString(profile));
+ final Profile p = BluetoothUtil.getProfile(proxy);
+ if (p == null) {
+ Log.w(TAG, "Unable get get Profile for " + profileToString(profile));
+ } else {
+ final boolean ok = connect ? p.connect(device) : p.disconnect(device);
+ if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " "
+ + (ok ? "succeeded" : "failed"));
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ if (DEBUG) Log.d(TAG, "onServiceDisconnected " + profileToString(profile));
+ }
+ }, profile);
+ }
}
@Override
public String getLastDeviceName() {
- return mLastDevice != null ? mLastDevice.getAliasName()
- : mBondedDevices.size() == 1 ? mBondedDevices.iterator().next().getAliasName()
- : null;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
-
- if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
- handleAdapterStateChange(
- intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR));
- }
- if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
- mConnecting = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1)
- == BluetoothAdapter.STATE_CONNECTING;
- mLastDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- }
- if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
- mLastDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- }
- fireCallbacks();
- updateBondedBluetoothDevices();
+ return mLastDevice != null ? mLastDevice.getAliasName() : null;
}
private void updateBondedBluetoothDevices() {
- mBondedDevices.clear();
-
- BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- Set<BluetoothDevice> devices = adapter.getBondedDevices();
- if (devices != null) {
- for (BluetoothDevice device : devices) {
- if (device.getBondState() != BluetoothDevice.BOND_NONE) {
- mBondedDevices.add(device);
- }
+ if (mAdapter == null) return;
+ final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices();
+ for (DeviceInfo info : mDeviceInfo.values()) {
+ info.bonded = false;
+ }
+ int bondedCount = 0;
+ BluetoothDevice lastBonded = null;
+ if (bondedDevices != null) {
+ for (BluetoothDevice bondedDevice : bondedDevices) {
+ final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE;
+ updateInfo(bondedDevice).bonded = bonded;
+ if (bonded) {
+ bondedCount++;
+ lastBonded = bondedDevice;
}
}
}
+ if (mLastDevice == null && bondedCount == 1) {
+ mLastDevice = lastBonded;
+ }
+ firePairedDevicesChanged();
}
- private void handleAdapterStateChange(int adapterState) {
- mEnabled = (adapterState == BluetoothAdapter.STATE_ON);
- }
-
- private void fireCallbacks() {
+ private void firePairedDevicesChanged() {
for (Callback cb : mCallbacks) {
- fireCallback(cb);
+ cb.onBluetoothPairedDevicesChanged();
}
}
- private void fireCallback(Callback cb) {
+ private void setAdapterState(int adapterState) {
+ final boolean enabled = adapterState == BluetoothAdapter.STATE_ON;
+ if (mEnabled == enabled) return;
+ mEnabled = enabled;
+ fireStateChange();
+ }
+
+ private void setConnecting(boolean connecting) {
+ if (mConnecting == connecting) return;
+ mConnecting = connecting;
+ fireStateChange();
+ }
+
+ private void fireStateChange() {
+ for (Callback cb : mCallbacks) {
+ fireStateChange(cb);
+ }
+ }
+
+ private void fireStateChange(Callback cb) {
cb.onBluetoothStateChange(mEnabled, mConnecting);
}
+
+ private final class Receiver extends BroadcastReceiver {
+ public void register() {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
+ filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED);
+ mContext.registerReceiver(this, filter);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
+ setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR));
+ if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled);
+ } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
+ final DeviceInfo info = updateInfo(device);
+ final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+ ERROR);
+ if (state != ERROR) {
+ info.connectionState = state;
+ }
+ mLastDevice = device;
+ if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED "
+ + connectionStateToString(state) + " " + deviceToString(device));
+ setConnecting(info.connectionState == BluetoothAdapter.STATE_CONNECTING);
+ } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) {
+ updateInfo(device);
+ mLastDevice = device;
+ } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
+ if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device);
+ // we'll update all bonded devices below
+ }
+ updateBondedBluetoothDevices();
+ }
+ }
+
+ private DeviceInfo updateInfo(BluetoothDevice device) {
+ DeviceInfo info = mDeviceInfo.get(device);
+ info = info != null ? info : new DeviceInfo();
+ mDeviceInfo.put(device, info);
+ return info;
+ }
+
+ private static class DeviceInfo {
+ int connectionState = BluetoothAdapter.STATE_DISCONNECTED;
+ boolean bonded; // per getBondedDevices
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
new file mode 100644
index 0000000..1b4be85
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothUtil.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothInputDevice;
+import android.bluetooth.BluetoothMap;
+import android.bluetooth.BluetoothPan;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.os.ParcelUuid;
+import android.text.TextUtils;
+
+public class BluetoothUtil {
+
+ public static String profileToString(int profile) {
+ if (profile == BluetoothProfile.HEADSET) return "HEADSET";
+ if (profile == BluetoothProfile.A2DP) return "A2DP";
+ if (profile == BluetoothProfile.AVRCP_CONTROLLER) return "AVRCP_CONTROLLER";
+ return "UNKNOWN";
+ }
+
+ public static String profileStateToString(int state) {
+ if (state == BluetoothProfile.STATE_CONNECTED) return "STATE_CONNECTED";
+ if (state == BluetoothProfile.STATE_CONNECTING) return "STATE_CONNECTING";
+ if (state == BluetoothProfile.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
+ if (state == BluetoothProfile.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
+ return "STATE_UNKNOWN";
+ }
+
+ public static String uuidToString(ParcelUuid uuid) {
+ if (BluetoothUuid.AudioSink.equals(uuid)) return "AudioSink";
+ if (BluetoothUuid.AudioSource.equals(uuid)) return "AudioSource";
+ if (BluetoothUuid.AdvAudioDist.equals(uuid)) return "AdvAudioDist";
+ if (BluetoothUuid.HSP.equals(uuid)) return "HSP";
+ if (BluetoothUuid.HSP_AG.equals(uuid)) return "HSP_AG";
+ if (BluetoothUuid.Handsfree.equals(uuid)) return "Handsfree";
+ if (BluetoothUuid.Handsfree_AG.equals(uuid)) return "Handsfree_AG";
+ if (BluetoothUuid.AvrcpController.equals(uuid)) return "AvrcpController";
+ if (BluetoothUuid.AvrcpTarget.equals(uuid)) return "AvrcpTarget";
+ if (BluetoothUuid.ObexObjectPush.equals(uuid)) return "ObexObjectPush";
+ if (BluetoothUuid.Hid.equals(uuid)) return "Hid";
+ if (BluetoothUuid.Hogp.equals(uuid)) return "Hogp";
+ if (BluetoothUuid.PANU.equals(uuid)) return "PANU";
+ if (BluetoothUuid.NAP.equals(uuid)) return "NAP";
+ if (BluetoothUuid.BNEP.equals(uuid)) return "BNEP";
+ if (BluetoothUuid.PBAP_PSE.equals(uuid)) return "PBAP_PSE";
+ if (BluetoothUuid.MAP.equals(uuid)) return "MAP";
+ if (BluetoothUuid.MNS.equals(uuid)) return "MNS";
+ if (BluetoothUuid.MAS.equals(uuid)) return "MAS";
+ return uuid != null ? uuid.toString() : null;
+ }
+
+ public static String connectionStateToString(int connectionState) {
+ if (connectionState == BluetoothAdapter.STATE_DISCONNECTED) return "STATE_DISCONNECTED";
+ if (connectionState == BluetoothAdapter.STATE_CONNECTED) return "STATE_CONNECTED";
+ if (connectionState == BluetoothAdapter.STATE_DISCONNECTING) return "STATE_DISCONNECTING";
+ if (connectionState == BluetoothAdapter.STATE_CONNECTING) return "STATE_CONNECTING";
+ return "ERROR";
+ }
+
+ public static String deviceToString(BluetoothDevice device) {
+ return device == null ? null : (device.getAddress() + '[' + device.getAliasName() + ']');
+ }
+
+ public static String uuidsToString(BluetoothDevice device) {
+ if (device == null) return null;
+ final ParcelUuid[] ids = device.getUuids();
+ if (ids == null) return null;
+ final String[] tokens = new String[ids.length];
+ for (int i = 0; i < tokens.length; i++) {
+ tokens[i] = uuidToString(ids[i]);
+ }
+ return TextUtils.join(",", tokens);
+ }
+
+ public static int uuidToProfile(ParcelUuid uuid) {
+ if (BluetoothUuid.AudioSink.equals(uuid)) return BluetoothProfile.A2DP;
+ if (BluetoothUuid.AdvAudioDist.equals(uuid)) return BluetoothProfile.A2DP;
+
+ if (BluetoothUuid.HSP.equals(uuid)) return BluetoothProfile.HEADSET;
+ if (BluetoothUuid.Handsfree.equals(uuid)) return BluetoothProfile.HEADSET;
+
+ if (BluetoothUuid.MAP.equals(uuid)) return BluetoothProfile.MAP;
+ if (BluetoothUuid.MNS.equals(uuid)) return BluetoothProfile.MAP;
+ if (BluetoothUuid.MAS.equals(uuid)) return BluetoothProfile.MAP;
+
+ if (BluetoothUuid.AvrcpController.equals(uuid)) return BluetoothProfile.AVRCP_CONTROLLER;
+
+ return 0;
+ }
+
+ public static Profile getProfile(BluetoothProfile p) {
+ if (p instanceof BluetoothA2dp) return newProfile((BluetoothA2dp) p);
+ if (p instanceof BluetoothHeadset) return newProfile((BluetoothHeadset) p);
+ if (p instanceof BluetoothA2dpSink) return newProfile((BluetoothA2dpSink) p);
+ if (p instanceof BluetoothHeadsetClient) return newProfile((BluetoothHeadsetClient) p);
+ if (p instanceof BluetoothInputDevice) return newProfile((BluetoothInputDevice) p);
+ if (p instanceof BluetoothMap) return newProfile((BluetoothMap) p);
+ if (p instanceof BluetoothPan) return newProfile((BluetoothPan) p);
+ return null;
+ }
+
+ private static Profile newProfile(final BluetoothA2dp a2dp) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return a2dp.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return a2dp.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothHeadset headset) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return headset.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return headset.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothA2dpSink sink) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return sink.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return sink.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothHeadsetClient client) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return client.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return client.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothInputDevice input) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return input.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return input.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothMap map) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return map.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return map.disconnect(device);
+ }
+ };
+ }
+
+ private static Profile newProfile(final BluetoothPan pan) {
+ return new Profile() {
+ @Override
+ public boolean connect(BluetoothDevice device) {
+ return pan.connect(device);
+ }
+
+ @Override
+ public boolean disconnect(BluetoothDevice device) {
+ return pan.disconnect(device);
+ }
+ };
+ }
+
+ // common abstraction for supported profiles
+ public interface Profile {
+ boolean connect(BluetoothDevice device);
+ boolean disconnect(BluetoothDevice device);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
index 0a48e34..d232bc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpNotificationView.java
@@ -26,6 +26,7 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
@@ -159,6 +160,21 @@
// ViewGroup methods
+ private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER =
+ new ViewOutlineProvider() {
+ @Override
+ public boolean getOutline(View view, Outline outline) {
+ int outlineLeft = view.getPaddingLeft();
+ int outlineTop = view.getPaddingTop();
+
+ // Apply padding to shadow.
+ outline.setRect(outlineLeft, outlineTop,
+ view.getWidth() - outlineLeft - view.getPaddingRight(),
+ view.getHeight() - outlineTop - view.getPaddingBottom());
+ return true;
+ }
+ };
+
@Override
public void onAttachedToWindow() {
float densityScale = getResources().getDisplayMetrics().density;
@@ -174,6 +190,7 @@
mExpandHelper = new ExpandHelper(getContext(), this, minHeight, maxHeight);
mContentHolder = (ViewGroup) findViewById(R.id.content_holder);
+ mContentHolder.setOutlineProvider(CONTENT_HOLDER_OUTLINE_PROVIDER);
if (mHeadsUp != null) {
// whoops, we're on already!
@@ -232,20 +249,6 @@
mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
}
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- Outline o = new Outline();
-
- // Apply padding to shadow.
- int outlineLeft = mContentHolder.getPaddingLeft();
- int outlineTop = mContentHolder.getPaddingTop();
- o.setRect(outlineLeft, outlineTop,
- mContentHolder.getWidth() - outlineLeft - mContentHolder.getPaddingRight(),
- mContentHolder.getHeight() - outlineTop - mContentHolder.getPaddingBottom());
- mContentHolder.setOutline(o);
- }
-
// ExpandHelper.Callback methods
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java
new file mode 100644
index 0000000..e7c4ede
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SplitClockView.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.text.format.DateFormat;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextClock;
+
+import com.android.systemui.R;
+
+/**
+ * Container for a clock which has two separate views for the clock itself and AM/PM indicator. This
+ * is used to scale the clock independently of AM/PM.
+ */
+public class SplitClockView extends LinearLayout {
+
+ private TextClock mTimeView;
+ private TextClock mAmPmView;
+
+ private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_TIME_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
+ || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ updatePatterns();
+ }
+ }
+ };
+
+ public SplitClockView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mTimeView = (TextClock) findViewById(R.id.time_view);
+ mAmPmView = (TextClock) findViewById(R.id.am_pm_view);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ getContext().registerReceiver(mIntentReceiver, filter, null, null);
+
+ updatePatterns();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ getContext().unregisterReceiver(mIntentReceiver);
+ }
+
+ private void updatePatterns() {
+ String formatString = DateFormat.getTimeFormatString(getContext());
+ int index = getAmPmPartEndIndex(formatString);
+ String timeString;
+ String amPmString;
+ if (index == -1) {
+ timeString = formatString;
+ amPmString = "";
+ } else {
+ timeString = formatString.substring(0, index);
+ amPmString = formatString.substring(index);
+ }
+ mTimeView.setFormat12Hour(timeString);
+ mTimeView.setFormat24Hour(timeString);
+ mAmPmView.setFormat12Hour(amPmString);
+ mAmPmView.setFormat24Hour(amPmString);
+ }
+
+ /**
+ * @return the index where the AM/PM part starts at the end in {@code formatString} including
+ * leading white spaces or {@code -1} if no AM/PM part is found or {@code formatString}
+ * doesn't end with AM/PM part
+ */
+ private static int getAmPmPartEndIndex(String formatString) {
+ boolean hasAmPm = false;
+ int length = formatString.length();
+ for (int i = length - 1; i >= 0; i--) {
+ char c = formatString.charAt(i);
+ boolean isAmPm = c == 'a';
+ boolean isWhitespace = Character.isWhitespace(c);
+ if (isAmPm) {
+ hasAmPm = true;
+ }
+ if (isAmPm || isWhitespace) {
+ continue;
+ }
+ if (i == length - 1) {
+
+ // First character was not AM/PM and not whitespace, so it's not ending with AM/PM.
+ return -1;
+ } else {
+
+ // If we have AM/PM at all, return last index, or -1 to indicate that it's not
+ // ending with AM/PM.
+ return hasAmPm ? i + 1 : -1;
+ }
+ }
+
+ // Only AM/PM and whitespaces? The whole string is AM/PM. Else: Only whitespaces in the
+ // string.
+ return hasAmPm ? 0 : -1;
+ }
+
+}
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 1721ec4..9bcffd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1317,7 +1317,8 @@
}
public int getPeekHeight() {
- return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize;
+ return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
+ + mCollapseSecondCardPadding;
}
private int clampPadding(int desiredPadding) {
@@ -1795,11 +1796,11 @@
}
public int getEmptyBottomMargin() {
- int emptyMargin = mMaxLayoutHeight - mContentHeight;
+ int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
if (needsHeightAdaption()) {
- emptyMargin = emptyMargin - mBottomStackSlowDownHeight - mBottomStackPeekSize;
+ emptyMargin -= mBottomStackSlowDownHeight;
} else {
- emptyMargin = emptyMargin - mBottomStackPeekSize;
+ emptyMargin -= mCollapseSecondCardPadding;
}
return Math.max(emptyMargin, 0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 08216c4..ad2cf75 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -85,6 +85,7 @@
private static final int FREE_DELAY = 10000;
private static final int TIMEOUT_DELAY = 3000;
private static final int TIMEOUT_DELAY_SHORT = 1500;
+ private static final int TIMEOUT_DELAY_COLLAPSED = 4500;
private static final int TIMEOUT_DELAY_EXPANDED = 10000;
private static final int MSG_VOLUME_CHANGED = 0;
@@ -263,7 +264,7 @@
synchronized (sConfirmSafeVolumeLock) {
sConfirmSafeVolumeDialog = null;
}
- mVolumePanel.forceTimeout();
+ mVolumePanel.forceTimeout(0);
mVolumePanel.updateStates();
}
}
@@ -293,7 +294,7 @@
public boolean onTouchEvent(MotionEvent event) {
if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE &&
sConfirmSafeVolumeDialog == null) {
- forceTimeout();
+ forceTimeout(0);
return true;
}
return false;
@@ -591,11 +592,18 @@
private void updateTimeoutDelay() {
mTimeoutDelay = mActiveStreamType == AudioManager.STREAM_MUSIC ? TIMEOUT_DELAY_SHORT
- : mZenPanelExpanded ? TIMEOUT_DELAY_EXPANDED : TIMEOUT_DELAY;
+ : mZenPanelExpanded ? TIMEOUT_DELAY_EXPANDED
+ : isZenPanelVisible() ? TIMEOUT_DELAY_COLLAPSED
+ : TIMEOUT_DELAY;
+ }
+
+ private boolean isZenPanelVisible() {
+ return mZenPanel != null && mZenPanel.getVisibility() == View.VISIBLE;
}
private void setZenPanelVisible(boolean visible) {
if (LOGD) Log.d(mTag, "setZenPanelVisible " + visible + " mZenPanel=" + mZenPanel);
+ final boolean changing = visible != isZenPanelVisible();
if (visible) {
if (mZenPanel == null) {
mZenPanel = (ZenModePanel) mZenPanelStub.inflate();
@@ -629,6 +637,10 @@
mZenPanel.setVisibility(View.GONE);
}
}
+ if (changing) {
+ updateTimeoutDelay();
+ resetTimeout();
+ }
}
public void updateStates() {
@@ -718,8 +730,8 @@
obtainMessage(MSG_DISPLAY_SAFE_VOLUME_WARNING, flags, 0).sendToTarget();
}
- public void postDismiss() {
- forceTimeout();
+ public void postDismiss(long delay) {
+ forceTimeout(delay);
}
public void postLayoutDirection(int layoutDirection) {
@@ -1198,16 +1210,17 @@
}
private void resetTimeout() {
- if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis());
+ if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis()
+ + " delay=" + mTimeoutDelay);
removeMessages(MSG_TIMEOUT);
sendEmptyMessageDelayed(MSG_TIMEOUT, mTimeoutDelay);
removeMessages(MSG_USER_ACTIVITY);
sendEmptyMessage(MSG_USER_ACTIVITY);
}
- private void forceTimeout() {
+ private void forceTimeout(long delay) {
removeMessages(MSG_TIMEOUT);
- sendEmptyMessage(MSG_TIMEOUT);
+ sendEmptyMessageDelayed(MSG_TIMEOUT, delay);
}
public ZenModeController getZenController() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 51be833..e4f5870 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -11,12 +11,14 @@
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
+import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -45,6 +47,7 @@
private static final int DEFAULT = 1; // enabled by default
private final Handler mHandler = new Handler();
+
private AudioManager mAudioManager;
private MediaSessionManager mMediaSessionManager;
private VolumeController mVolumeController;
@@ -52,6 +55,7 @@
private VolumePanel mDialogPanel;
private VolumePanel mPanel;
+ private int mDismissDelay;
@Override
public void start() {
@@ -79,6 +83,7 @@
}
private void initPanel() {
+ mDismissDelay = mContext.getResources().getInteger(R.integer.volume_panel_dismiss_delay);
mPanel = new VolumePanel(mContext, null, new ZenModeControllerImpl(mContext, mHandler));
mPanel.setCallback(new VolumePanel.Callback() {
@Override
@@ -109,15 +114,20 @@
private final Runnable mStartZenSettings = new Runnable() {
@Override
public void run() {
- mDialogPanel.postDismiss();
- try {
- // Dismiss the lock screen when Settings starts.
- ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
- } catch (RemoteException e) {
- }
- final Intent intent = ZenModePanel.ZEN_SETTINGS;
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ // Dismiss the lock screen when Settings starts.
+ ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
+ } catch (RemoteException e) {
+ }
+ final Intent intent = ZenModePanel.ZEN_SETTINGS;
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ }
+ });
+ mDialogPanel.postDismiss(mDismissDelay);
}
};
@@ -153,7 +163,7 @@
@Override
public void dismiss() throws RemoteException {
- mPanel.postDismiss();
+ mPanel.postDismiss(0);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 6bb9765..9c166ac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -215,10 +215,12 @@
}
private void refreshExitConditionText() {
+ final String forever = mContext.getString(R.string.zen_mode_forever);
if (mExitConditionId == null) {
- mExitConditionText = mContext.getString(R.string.zen_mode_forever);
+ mExitConditionText = forever;
} else if (ZenModeConfig.isValidCountdownConditionId(mExitConditionId)) {
- mExitConditionText = parseExistingTimeCondition(mExitConditionId).summary;
+ final Condition condition = parseExistingTimeCondition(mExitConditionId);
+ mExitConditionText = condition != null ? condition.summary : forever;
} else {
mExitConditionText = "(until condition ends)"; // TODO persist current description
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 28f7a0f..ef15a80 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -5506,12 +5506,12 @@
// prevent status bar interaction from clearing certain flags
boolean statusBarHasFocus = win.getAttrs().type == TYPE_STATUS_BAR;
- if (statusBarHasFocus) {
+ if (statusBarHasFocus && !isStatusBarKeyguard()) {
int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
- if (!isStatusBarKeyguard() || mHideLockScreen) {
+ if (mHideLockScreen) {
flags |= View.STATUS_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSLUCENT;
}
vis = (vis & ~flags) | (oldVis & flags);
diff --git a/rs/java/android/renderscript/ScriptIntrinsicBlend.java b/rs/java/android/renderscript/ScriptIntrinsicBlend.java
index d4038c2..906e0f6 100644
--- a/rs/java/android/renderscript/ScriptIntrinsicBlend.java
+++ b/rs/java/android/renderscript/ScriptIntrinsicBlend.java
@@ -99,7 +99,7 @@
* @param opt LaunchOptions for clipping
*/
public void forEachSrc(Allocation ain, Allocation aout, Script.LaunchOptions opt) {
- forEachDst(ain, aout, null);
+ blend(1, ain, aout, null);
}
/**
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ea05b98..6554ed3 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -179,8 +179,6 @@
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
-import static android.net.ConnectivityManager.INVALID_NET_ID;
-
/**
* @hide
*/
@@ -1841,8 +1839,8 @@
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
- LinkProperties lp = null;
- int netId = INVALID_NET_ID;
+ LinkProperties lp;
+ int netId;
synchronized (nai) {
lp = nai.linkProperties;
netId = nai.network.netId;
@@ -2124,16 +2122,6 @@
"ConnectivityService");
}
- private void enforceMarkNetworkSocketPermission() {
- //Media server special case
- if (Binder.getCallingUid() == Process.MEDIA_UID) {
- return;
- }
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.MARK_NETWORK_SOCKET,
- "ConnectivityService");
- }
-
/**
* Handle a {@code DISCONNECTED} event. If this pertains to the non-active
* network, we ignore it. If it is for the active network, we send out a
@@ -2239,7 +2227,7 @@
} catch (Exception e) {
loge("Exception removing network: " + e);
} finally {
- mNetTrackers[prevNetType].setNetId(INVALID_NET_ID);
+// mNetTrackers[prevNetType].setNetId(INVALID_NET_ID);
}
}
@@ -4086,23 +4074,6 @@
}
}
- @Override
- public void markSocketAsUser(ParcelFileDescriptor socket, int uid) {
- enforceMarkNetworkSocketPermission();
- final long token = Binder.clearCallingIdentity();
- try {
- int mark = mNetd.getMarkForUid(uid);
- // Clear the mark on the socket if no mark is needed to prevent socket reuse issues
- if (mark == -1) {
- mark = 0;
- }
- NetworkUtils.markSocket(socket.getFd(), mark);
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
/**
* Configure a TUN interface and return its file descriptor. Parameters
* are encoded and opaque to this class. This method is used by VpnBuilder
@@ -5322,9 +5293,19 @@
setAlarm(samplingIntervalInSeconds * 1000, mSampleIntervalElapsedIntent);
}
+ /**
+ * Sets a network sampling alarm.
+ */
void setAlarm(int timeoutInMilliseconds, PendingIntent intent) {
long wakeupTime = SystemClock.elapsedRealtime() + timeoutInMilliseconds;
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime, intent);
+ int alarmType;
+ if (Resources.getSystem().getBoolean(
+ R.bool.config_networkSamplingWakesDevice)) {
+ alarmType = AlarmManager.ELAPSED_REALTIME_WAKEUP;
+ } else {
+ alarmType = AlarmManager.ELAPSED_REALTIME;
+ }
+ mAlarmManager.set(alarmType, wakeupTime, intent);
}
private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos =
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index 8fb2e9f..d4f141d 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -148,7 +148,6 @@
static final int MSG_UNBIND_METHOD = 3000;
static final int MSG_BIND_METHOD = 3010;
static final int MSG_SET_ACTIVE = 3020;
- static final int MSG_SET_CURSOR_ANCHOR_MONITOR_MODE = 3030;
static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 3040;
static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
@@ -2338,26 +2337,6 @@
}
}
- @Override
- public void setCursorAnchorMonitorMode(IBinder token, int monitorMode) {
- if (DEBUG) {
- Slog.d(TAG, "setCursorAnchorMonitorMode: monitorMode=" + monitorMode);
- }
- if (!calledFromValidUser()) {
- return;
- }
- synchronized (mMethodMap) {
- if (!calledWithValidToken(token)) {
- final int uid = Binder.getCallingUid();
- Slog.e(TAG, "Ignoring setCursorAnchorMonitorMode due to an invalid token. uid:"
- + uid + " token:" + token);
- return;
- }
- executeOrSendMessage(mCurMethod, mCaller.obtainMessageIO(
- MSG_SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, mCurClient));
- }
- }
-
private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
synchronized (mMethodMap) {
setInputMethodWithSubtypeIdLocked(token, id, subtypeId);
@@ -2595,15 +2574,6 @@
+ ((ClientState)msg.obj).uid);
}
return true;
- case MSG_SET_CURSOR_ANCHOR_MONITOR_MODE:
- try {
- ((ClientState)msg.obj).client.setCursorAnchorMonitorMode(msg.arg1);
- } catch (RemoteException e) {
- Slog.w(TAG, "Got RemoteException sending setCursorAnchorMonitorMode "
- + "notification to pid " + ((ClientState)msg.obj).pid
- + " uid " + ((ClientState)msg.obj).uid);
- }
- return true;
case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
final int sequenceNumber = msg.arg1;
final IInputMethodClient client = (IInputMethodClient)msg.obj;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 10382ba..bae2d22 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -16,6 +16,28 @@
package com.android.server;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.location.ActivityRecognitionProxy;
+import com.android.server.location.FlpHardwareProvider;
+import com.android.server.location.FusedProxy;
+import com.android.server.location.GeocoderProxy;
+import com.android.server.location.GeofenceManager;
+import com.android.server.location.GeofenceProxy;
+import com.android.server.location.GpsLocationProvider;
+import com.android.server.location.GpsMeasurementsProvider;
+import com.android.server.location.LocationBlacklist;
+import com.android.server.location.LocationFudger;
+import com.android.server.location.LocationProviderInterface;
+import com.android.server.location.LocationProviderProxy;
+import com.android.server.location.LocationRequestStatistics;
+import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
+import com.android.server.location.LocationRequestStatistics.PackageStatistics;
+import com.android.server.location.MockProvider;
+import com.android.server.location.PassiveProvider;
+
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -32,10 +54,12 @@
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.hardware.location.ActivityRecognitionHardware;
import android.location.Address;
import android.location.Criteria;
import android.location.GeocoderParams;
import android.location.Geofence;
+import android.location.IGpsMeasurementsListener;
import android.location.IGpsStatusListener;
import android.location.IGpsStatusProvider;
import android.location.ILocationListener;
@@ -62,26 +86,6 @@
import android.util.Log;
import android.util.Slog;
-import com.android.internal.content.PackageMonitor;
-import com.android.internal.location.ProviderProperties;
-import com.android.internal.location.ProviderRequest;
-import com.android.internal.os.BackgroundThread;
-import com.android.server.location.FlpHardwareProvider;
-import com.android.server.location.FusedProxy;
-import com.android.server.location.GeocoderProxy;
-import com.android.server.location.GeofenceProxy;
-import com.android.server.location.GeofenceManager;
-import com.android.server.location.GpsLocationProvider;
-import com.android.server.location.LocationBlacklist;
-import com.android.server.location.LocationFudger;
-import com.android.server.location.LocationProviderInterface;
-import com.android.server.location.LocationProviderProxy;
-import com.android.server.location.LocationRequestStatistics;
-import com.android.server.location.LocationRequestStatistics.PackageProviderKey;
-import com.android.server.location.LocationRequestStatistics.PackageStatistics;
-import com.android.server.location.MockProvider;
-import com.android.server.location.PassiveProvider;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -154,6 +158,7 @@
private LocationWorkerHandler mLocationHandler;
private PassiveProvider mPassiveProvider; // track passive provider for special cases
private LocationBlacklist mBlacklist;
+ private GpsMeasurementsProvider mGpsMeasurementsProvider;
// --- fields below are protected by mLock ---
// Set of providers that are explicitly enabled
@@ -403,6 +408,7 @@
addProviderLocked(gpsProvider);
mRealProviders.put(LocationManager.GPS_PROVIDER, gpsProvider);
}
+ mGpsMeasurementsProvider = gpsProvider.getGpsMeasurementsProvider();
/*
Load package name(s) containing location provider support.
@@ -471,7 +477,7 @@
Slog.e(TAG, "no geocoder provider found");
}
- // bind to fused provider if supported
+ // bind to fused hardware provider if supported
if (FlpHardwareProvider.isSupported()) {
FlpHardwareProvider flpHardwareProvider =
FlpHardwareProvider.getInstance(mContext);
@@ -501,6 +507,23 @@
Slog.e(TAG, "FLP HAL not supported.");
}
+ // bind to the hardware activity recognition if supported
+ if (ActivityRecognitionHardware.isSupported()) {
+ ActivityRecognitionProxy proxy = ActivityRecognitionProxy.createAndBind(
+ mContext,
+ mLocationHandler,
+ ActivityRecognitionHardware.getInstance(mContext),
+ com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
+ com.android.internal.R.string.config_activityRecognitionHardwarePackageName,
+ com.android.internal.R.array.config_locationProviderPackageNames);
+
+ if (proxy == null) {
+ Slog.e(TAG, "Unable to bind ActivityRecognitionProxy.");
+ }
+ } else {
+ Slog.e(TAG, "Hardware Activity-Recognition not supported.");
+ }
+
String[] testProviderStrings = resources.getStringArray(
com.android.internal.R.array.config_testLocationProviders);
for (String testProviderString : testProviderStrings) {
@@ -1804,6 +1827,36 @@
}
@Override
+ public boolean addGpsMeasurementsListener(
+ IGpsMeasurementsListener listener,
+ String packageName) {
+ int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+ checkResolutionLevelIsSufficientForProviderUse(
+ allowedResolutionLevel,
+ LocationManager.GPS_PROVIDER);
+
+ int uid = Binder.getCallingUid();
+ long identity = Binder.clearCallingIdentity();
+ boolean hasLocationAccess;
+ try {
+ hasLocationAccess = checkLocationAccess(uid, packageName, allowedResolutionLevel);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ if (!hasLocationAccess) {
+ return false;
+ }
+
+ return mGpsMeasurementsProvider.addListener(listener);
+ }
+
+ @Override
+ public boolean removeGpsMeasurementsListener(IGpsMeasurementsListener listener) {
+ return mGpsMeasurementsProvider.removeListener(listener);
+ }
+
+ @Override
public boolean sendExtraCommand(String provider, String command, Bundle extras) {
if (provider == null) {
// throw NullPointerException to remain compatible with previous implementation
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index c9f40cf..2434f712 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -26,7 +26,6 @@
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_TETHERING;
import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
-import static com.android.server.NetworkManagementService.NetdResponseCode.GetMarkResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult;
@@ -144,7 +143,6 @@
public static final int TetheringStatsResult = 221;
public static final int DnsProxyQueryResult = 222;
public static final int ClatdStatusResult = 223;
- public static final int GetMarkResult = 225;
public static final int InterfaceChange = 600;
public static final int BandwidthControl = 601;
@@ -1750,56 +1748,6 @@
}
@Override
- public int getMarkForUid(int uid) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("interface", "fwmark", "get", "mark", uid);
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- event.checkCode(GetMarkResult);
- return Integer.parseInt(event.getMessage());
- }
-
- @Override
- public int getMarkForProtect() {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- final NativeDaemonEvent event;
- try {
- event = mConnector.execute("interface", "fwmark", "get", "protect");
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- event.checkCode(GetMarkResult);
- return Integer.parseInt(event.getMessage());
- }
-
- @Override
- public void setMarkedForwardingRoute(String iface, RouteInfo route) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- LinkAddress dest = route.getDestinationLinkAddress();
- mConnector.execute("interface", "fwmark", "route", "add", iface,
- dest.getAddress().getHostAddress(), dest.getPrefixLength());
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- @Override
- public void clearMarkedForwardingRoute(String iface, RouteInfo route) {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- try {
- LinkAddress dest = route.getDestinationLinkAddress();
- mConnector.execute("interface", "fwmark", "route", "remove", iface,
- dest.getAddress().getHostAddress(), dest.getPrefixLength());
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
- }
- }
-
- @Override
public void setHostExemption(LinkAddress host) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
try {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 69262c8..d6e51f3 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -961,7 +961,7 @@
+ " type=" + resolvedType + " callingUid=" + callingUid);
userId = mAm.handleIncomingUser(callingPid, callingUid, userId,
- false, true, "service", null);
+ false, ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE, "service", null);
ServiceMap smap = getServiceMap(userId);
final ComponentName comp = service.getComponent();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e8e1536..cdb3835 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -41,7 +42,9 @@
import android.os.PersistableBundle;
import android.service.voice.IVoiceInteractionSession;
import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.SparseIntArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
@@ -343,6 +346,11 @@
// How many bytes to write into the dropbox log before truncating
static final int DROPBOX_MAX_SIZE = 256 * 1024;
+ // Access modes for handleIncomingUser.
+ static final int ALLOW_NON_FULL = 0;
+ static final int ALLOW_NON_FULL_IN_PROFILE = 1;
+ static final int ALLOW_FULL_ONLY = 2;
+
/** All system services */
SystemServiceManager mSystemServiceManager;
@@ -413,6 +421,7 @@
* List of intents that were used to start the most recent tasks.
*/
ArrayList<TaskRecord> mRecentTasks;
+ ArraySet<TaskRecord> mTmpRecents = new ArraySet<TaskRecord>();
public class PendingAssistExtras extends Binder implements Runnable {
public final ActivityRecord activity;
@@ -1094,6 +1103,12 @@
int mCurrentUserId = 0;
int[] mCurrentProfileIds = new int[] {UserHandle.USER_OWNER}; // Accessed by ActivityStack
+
+ /**
+ * Mapping from each known user ID to the profile group ID it is associated with.
+ */
+ SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
+
private UserManagerService mUserManager;
private final class AppDeathRecipient implements IBinder.DeathRecipient {
@@ -3340,7 +3355,7 @@
String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivity");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, true, "startActivity", null);
+ false, ALLOW_FULL_ONLY, "startActivity", null);
// TODO: Switch to user app stacks here.
return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
@@ -3354,12 +3369,12 @@
ParcelFileDescriptor profileFd, Bundle options, int userId) {
enforceNotIsolatedCaller("startActivityAndWait");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, true, "startActivityAndWait", null);
+ false, ALLOW_FULL_ONLY, "startActivityAndWait", null);
WaitResult res = new WaitResult();
// TODO: Switch to user app stacks here.
mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
- res, null, options, UserHandle.getCallingUserId(), null);
+ res, null, options, userId, null);
return res;
}
@@ -3370,7 +3385,7 @@
Bundle options, int userId) {
enforceNotIsolatedCaller("startActivityWithConfig");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, true, "startActivityWithConfig", null);
+ false, ALLOW_FULL_ONLY, "startActivityWithConfig", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
@@ -3428,7 +3443,7 @@
throw new NullPointerException("null session or interactor");
}
userId = handleIncomingUser(callingPid, callingUid, userId,
- false, true, "startVoiceActivity", null);
+ false, ALLOW_FULL_ONLY, "startVoiceActivity", null);
// TODO: Switch to user app stacks here.
return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent,
resolvedType, session, interactor, null, null, 0, startFlags,
@@ -3547,7 +3562,7 @@
IActivityContainer container) {
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, true, "startActivityInPackage", null);
+ false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, resolvedType,
@@ -3562,7 +3577,7 @@
int userId) {
enforceNotIsolatedCaller("startActivities");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, true, "startActivity", null);
+ false, ALLOW_FULL_ONLY, "startActivity", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivities(caller, -1, callingPackage, intents,
resolvedTypes, resultTo, options, userId);
@@ -3574,7 +3589,7 @@
Bundle options, int userId) {
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, true, "startActivityInPackage", null);
+ false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
// TODO: Switch to user app stacks here.
int ret = mStackSupervisor.startActivities(null, uid, callingPackage, intents, resolvedTypes,
resultTo, options, userId);
@@ -3598,7 +3613,7 @@
int maxRecents = task.maxRecents - 1;
for (int i=0; i<N; i++) {
- TaskRecord tr = mRecentTasks.get(i);
+ final TaskRecord tr = mRecentTasks.get(i);
if (task != tr) {
if (task.userId != tr.userId) {
continue;
@@ -3631,6 +3646,9 @@
// and their maxRecents has been reached.
tr.disposeThumbnail();
mRecentTasks.remove(i);
+ if (task != tr) {
+ tr.closeRecentsChain();
+ }
i--;
N--;
if (task.intent == null) {
@@ -3641,7 +3659,9 @@
mTaskPersister.notify(tr, false);
}
if (N >= MAX_RECENT_TASKS) {
- mRecentTasks.remove(N-1).disposeThumbnail();
+ final TaskRecord tr = mRecentTasks.remove(N - 1);
+ tr.disposeThumbnail();
+ tr.closeRecentsChain();
}
mRecentTasks.add(0, task);
}
@@ -4496,7 +4516,7 @@
int uid = Binder.getCallingUid();
int pid = Binder.getCallingPid();
userId = handleIncomingUser(pid, uid,
- userId, false, true, "clearApplicationUserData", null);
+ userId, false, ALLOW_FULL_ONLY, "clearApplicationUserData", null);
long callingId = Binder.clearCallingIdentity();
try {
IPackageManager pm = AppGlobals.getPackageManager();
@@ -4564,7 +4584,7 @@
}
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, true, true, "killBackgroundProcesses", null);
+ userId, true, ALLOW_FULL_ONLY, "killBackgroundProcesses", null);
long callingId = Binder.clearCallingIdentity();
try {
IPackageManager pm = AppGlobals.getPackageManager();
@@ -4647,7 +4667,7 @@
}
final int callingPid = Binder.getCallingPid();
userId = handleIncomingUser(callingPid, Binder.getCallingUid(),
- userId, true, true, "forceStopPackage", null);
+ userId, true, ALLOW_FULL_ONLY, "forceStopPackage", null);
long callingId = Binder.clearCallingIdentity();
try {
IPackageManager pm = AppGlobals.getPackageManager();
@@ -5714,8 +5734,8 @@
int callingUid = Binder.getCallingUid();
int origUserId = userId;
userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
- type == ActivityManager.INTENT_SENDER_BROADCAST, false,
- "getIntentSender", null);
+ type == ActivityManager.INTENT_SENDER_BROADCAST,
+ ALLOW_NON_FULL, "getIntentSender", null);
if (origUserId == UserHandle.USER_CURRENT) {
// We don't want to evaluate this until the pending intent is
// actually executed. However, we do want to always do the
@@ -7267,8 +7287,7 @@
tr.updateTaskDescription();
// Compose the recent task info
- ActivityManager.RecentTaskInfo rti
- = new ActivityManager.RecentTaskInfo();
+ ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
rti.id = tr.getTopActivity() == null ? -1 : tr.taskId;
rti.persistentId = tr.taskId;
rti.baseIntent = new Intent(tr.getBaseIntent());
@@ -7279,19 +7298,20 @@
rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
rti.firstActiveTime = tr.firstActiveTime;
rti.lastActiveTime = tr.lastActiveTime;
+ rti.affiliatedTaskId = tr.mAffiliatedTaskId;
return rti;
}
@Override
- public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
- int flags, int userId) {
+ public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
final int callingUid = Binder.getCallingUid();
userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
- false, true, "getRecentTasks", null);
+ false, ALLOW_FULL_ONLY, "getRecentTasks", null);
+ final boolean includeProfiles = (flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0;
+ final boolean withExcluded = (flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0;
synchronized (this) {
- final boolean allowed = checkCallingPermission(
- android.Manifest.permission.GET_TASKS)
+ final boolean allowed = checkCallingPermission(android.Manifest.permission.GET_TASKS)
== PackageManager.PERMISSION_GRANTED;
if (!allowed) {
Slog.w(TAG, "getRecentTasks: caller " + callingUid
@@ -7309,12 +7329,60 @@
maxNum < N ? maxNum : N);
final Set<Integer> includedUsers;
- if ((flags & ActivityManager.RECENT_INCLUDE_PROFILES) != 0) {
+ if (includeProfiles) {
includedUsers = getProfileIdsLocked(userId);
} else {
includedUsers = new HashSet<Integer>();
}
includedUsers.add(Integer.valueOf(userId));
+
+ // Regroup affiliated tasks together.
+ for (int i = 0; i < N; ) {
+ TaskRecord task = mRecentTasks.remove(i);
+ if (mTmpRecents.contains(task)) {
+ continue;
+ }
+ int affiliatedTaskId = task.mAffiliatedTaskId;
+ while (true) {
+ TaskRecord next = task.mNextAffiliate;
+ if (next == null) {
+ break;
+ }
+ if (next.mAffiliatedTaskId != affiliatedTaskId) {
+ Slog.e(TAG, "Error in Recents: next.affiliatedTaskId=" +
+ next.mAffiliatedTaskId + " affiliatedTaskId=" + affiliatedTaskId);
+ task.setNextAffiliate(null);
+ if (next.mPrevAffiliate == task) {
+ next.setPrevAffiliate(null);
+ }
+ break;
+ }
+ if (next.mPrevAffiliate != task) {
+ Slog.e(TAG, "Error in Recents chain prev.mNextAffiliate=" +
+ next.mPrevAffiliate + " task=" + task);
+ next.setPrevAffiliate(null);
+ break;
+ }
+ if (!mRecentTasks.contains(next)) {
+ Slog.e(TAG, "Error in Recents: next=" + next + " not in mRecentTasks");
+ task.setNextAffiliate(null);
+ if (next.mPrevAffiliate == task) {
+ next.setPrevAffiliate(null);
+ }
+ break;
+ }
+ task = next;
+ }
+ // task is now the end of the list
+ do {
+ mRecentTasks.remove(task);
+ mRecentTasks.add(i++, task);
+ mTmpRecents.add(task);
+ } while ((task = task.mPrevAffiliate) != null);
+ }
+ mTmpRecents.clear();
+ // mRecentTasks is now in sorted, affiliated order.
+
for (int i=0; i<N && maxNum > 0; i++) {
TaskRecord tr = mRecentTasks.get(i);
// Only add calling user or related users recent tasks
@@ -7327,10 +7395,10 @@
// we should exclude the entry.
if (i == 0
- || ((flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0)
+ || withExcluded
|| (tr.intent == null)
- || ((tr.intent.getFlags()
- &Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)) {
+ || ((tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+ == 0)) {
if (!allowed) {
// If the caller doesn't have the GET_TASKS permission, then only
// allow them to see a small subset of tasks -- their own and home.
@@ -7426,6 +7494,7 @@
private void cleanUpRemovedTaskLocked(TaskRecord tr, int flags) {
tr.disposeThumbnail();
mRecentTasks.remove(tr);
+ tr.closeRecentsChain();
final boolean killProcesses = (flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0;
Intent baseIntent = new Intent(
tr.intent != null ? tr.intent : tr.affinityIntent);
@@ -7958,14 +8027,23 @@
boolean checkedGrants = false;
if (checkUser) {
// Looking for cross-user grants before enforcing the typical cross-users permissions
- if (UserHandle.getUserId(callingUid) != userId) {
- if (checkAuthorityGrants(callingUid, cpi, userId, checkUser)) {
+ int tmpTargetUserId = unsafeConvertIncomingUser(UserHandle.getUserId(callingUid));
+ if (tmpTargetUserId != userId) {
+ if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
return null;
}
checkedGrants = true;
}
userId = handleIncomingUser(callingPid, callingUid, userId,
- false, false, "checkContentProviderPermissionLocked " + cpi.authority, null);
+ false, ALLOW_NON_FULL_IN_PROFILE,
+ "checkContentProviderPermissionLocked " + cpi.authority, null);
+ if (userId != tmpTargetUserId) {
+ // When we actually went to determine the final targer user ID, this ended
+ // up different than our initial check for the authority. This is because
+ // they had asked for USER_CURRENT_OR_SELF and we ended up switching to
+ // SELF. So we need to re-check the grants again.
+ checkedGrants = false;
+ }
}
if (checkComponentPermission(cpi.readPermission, callingPid, callingUid,
cpi.applicationInfo.uid, cpi.exported)
@@ -8024,7 +8102,8 @@
boolean checkAuthorityGrants(int callingUid, ProviderInfo cpi, int userId, boolean checkUser) {
final ArrayMap<GrantUri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
if (perms != null) {
- for (GrantUri grantUri : perms.keySet()) {
+ for (int i=perms.size()-1; i>=0; i--) {
+ GrantUri grantUri = perms.keyAt(i);
if (grantUri.sourceUserId == userId || !checkUser) {
if (matchesProvider(grantUri.uri, cpi)) {
return true;
@@ -8439,7 +8518,7 @@
enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
"Do not have permission in call getContentProviderExternal()");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
- false, true, "getContentProvider", null);
+ false, ALLOW_FULL_ONLY, "getContentProvider", null);
return getContentProviderExternalUnchecked(name, token, userId);
}
@@ -8733,7 +8812,7 @@
public String getProviderMimeType(Uri uri, int userId) {
enforceNotIsolatedCaller("getProviderMimeType");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, false, true, "getProviderMimeType", null);
+ userId, false, ALLOW_NON_FULL_IN_PROFILE, "getProviderMimeType", null);
final String name = uri.getAuthority();
final long ident = Binder.clearCallingIdentity();
ContentProviderHolder holder = null;
@@ -9899,6 +9978,10 @@
return;
}
+ // Make sure we have the current profile info, since it is needed for
+ // security checks.
+ updateCurrentProfileIdsLocked();
+
if (mRecentTasks == null) {
mRecentTasks = mTaskPersister.restoreTasksLocked();
if (!mRecentTasks.isEmpty()) {
@@ -10131,7 +10214,7 @@
throws RemoteException {
}
}, 0, null, null,
- android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
+ INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
} catch (Throwable t) {
Slog.wtf(TAG, "Failed sending first user broadcasts", t);
@@ -11578,6 +11661,17 @@
if (dumpAll) {
pw.print(" mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
}
+ synchronized (mUserProfileGroupIdsSelfLocked) {
+ if (mUserProfileGroupIdsSelfLocked.size() > 0) {
+ pw.println(" mUserProfileGroupIds:");
+ for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
+ pw.print(" User #");
+ pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
+ pw.print(" -> profile #");
+ pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
+ }
+ }
+ }
}
if (mHomeProcess != null && (dumpPackage == null
|| mHomeProcess.pkgList.containsKey(dumpPackage))) {
@@ -13329,6 +13423,7 @@
// SERVICES
// =========================================================
+ @Override
public List<ActivityManager.RunningServiceInfo> getServices(int maxNum,
int flags) {
enforceNotIsolatedCaller("getServices");
@@ -13337,13 +13432,15 @@
}
}
+ @Override
public PendingIntent getRunningServiceControlPanel(ComponentName name) {
enforceNotIsolatedCaller("getRunningServiceControlPanel");
synchronized (this) {
return mServices.getRunningServiceControlPanelLocked(name);
}
}
-
+
+ @Override
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, int userId) {
enforceNotIsolatedCaller("startService");
@@ -13378,6 +13475,7 @@
}
}
+ @Override
public int stopService(IApplicationThread caller, Intent service,
String resolvedType, int userId) {
enforceNotIsolatedCaller("stopService");
@@ -13391,6 +13489,7 @@
}
}
+ @Override
public IBinder peekService(Intent service, String resolvedType) {
enforceNotIsolatedCaller("peekService");
// Refuse possible leaked file descriptors
@@ -13402,6 +13501,7 @@
}
}
+ @Override
public boolean stopServiceToken(ComponentName className, IBinder token,
int startId) {
synchronized(this) {
@@ -13409,6 +13509,7 @@
}
}
+ @Override
public void setServiceForeground(ComponentName className, IBinder token,
int id, Notification notification, boolean removeNotification) {
synchronized(this) {
@@ -13417,59 +13518,97 @@
}
}
+ @Override
public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
boolean requireFull, String name, String callerPackage) {
+ return handleIncomingUser(callingPid, callingUid, userId, allowAll,
+ requireFull ? ALLOW_FULL_ONLY : ALLOW_NON_FULL, name, callerPackage);
+ }
+
+ int unsafeConvertIncomingUser(int userId) {
+ return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
+ ? mCurrentUserId : userId;
+ }
+
+ int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
+ int allowMode, String name, String callerPackage) {
final int callingUserId = UserHandle.getUserId(callingUid);
- if (callingUserId != userId) {
- if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
- if ((requireFull || checkComponentPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS,
- callingPid, callingUid, -1, true) != PackageManager.PERMISSION_GRANTED)
- && checkComponentPermission(INTERACT_ACROSS_USERS_FULL,
- callingPid, callingUid, -1, true)
- != PackageManager.PERMISSION_GRANTED) {
- if (userId == UserHandle.USER_CURRENT_OR_SELF) {
- // In this case, they would like to just execute as their
- // owner user instead of failing.
- userId = callingUserId;
- } else {
- StringBuilder builder = new StringBuilder(128);
- builder.append("Permission Denial: ");
- builder.append(name);
- if (callerPackage != null) {
- builder.append(" from ");
- builder.append(callerPackage);
- }
- builder.append(" asks to run as user ");
- builder.append(userId);
- builder.append(" but is calling from user ");
- builder.append(UserHandle.getUserId(callingUid));
- builder.append("; this requires ");
- builder.append(INTERACT_ACROSS_USERS_FULL);
- if (!requireFull) {
- builder.append(" or ");
- builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
- }
- String msg = builder.toString();
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
+ if (callingUserId == userId) {
+ return userId;
+ }
+
+ // Note that we may be accessing mCurrentUserId outside of a lock...
+ // shouldn't be a big deal, if this is being called outside
+ // of a locked context there is intrinsically a race with
+ // the value the caller will receive and someone else changing it.
+ // We assume that USER_CURRENT_OR_SELF will use the current user; later
+ // we will switch to the calling user if access to the current user fails.
+ int targetUserId = unsafeConvertIncomingUser(userId);
+
+ if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+ final boolean allow;
+ if (checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
+ callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
+ // If the caller has this permission, they always pass go. And collect $200.
+ allow = true;
+ } else if (allowMode == ALLOW_FULL_ONLY) {
+ // We require full access, sucks to be you.
+ allow = false;
+ } else if (checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
+ callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+ // If the caller does not have either permission, they are always doomed.
+ allow = false;
+ } else if (allowMode == ALLOW_NON_FULL) {
+ // We are blanket allowing non-full access, you lucky caller!
+ allow = true;
+ } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
+ // We may or may not allow this depending on whether the two users are
+ // in the same profile.
+ synchronized (mUserProfileGroupIdsSelfLocked) {
+ int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
+ UserInfo.NO_PROFILE_GROUP_ID);
+ int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
+ UserInfo.NO_PROFILE_GROUP_ID);
+ allow = callingProfile != UserInfo.NO_PROFILE_GROUP_ID
+ && callingProfile == targetProfile;
+ }
+ } else {
+ throw new IllegalArgumentException("Unknown mode: " + allowMode);
+ }
+ if (!allow) {
+ if (userId == UserHandle.USER_CURRENT_OR_SELF) {
+ // In this case, they would like to just execute as their
+ // owner user instead of failing.
+ targetUserId = callingUserId;
+ } else {
+ StringBuilder builder = new StringBuilder(128);
+ builder.append("Permission Denial: ");
+ builder.append(name);
+ if (callerPackage != null) {
+ builder.append(" from ");
+ builder.append(callerPackage);
}
+ builder.append(" asks to run as user ");
+ builder.append(userId);
+ builder.append(" but is calling from user ");
+ builder.append(UserHandle.getUserId(callingUid));
+ builder.append("; this requires ");
+ builder.append(INTERACT_ACROSS_USERS_FULL);
+ if (allowMode != ALLOW_FULL_ONLY) {
+ builder.append(" or ");
+ builder.append(INTERACT_ACROSS_USERS);
+ }
+ String msg = builder.toString();
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
}
}
- if (userId == UserHandle.USER_CURRENT
- || userId == UserHandle.USER_CURRENT_OR_SELF) {
- // Note that we may be accessing this outside of a lock...
- // shouldn't be a big deal, if this is being called outside
- // of a locked context there is intrinsically a race with
- // the value the caller will receive and someone else changing it.
- userId = mCurrentUserId;
- }
- if (!allowAll && userId < 0) {
- throw new IllegalArgumentException(
- "Call does not support special user #" + userId);
- }
}
- return userId;
+ if (!allowAll && targetUserId < 0) {
+ throw new IllegalArgumentException(
+ "Call does not support special user #" + targetUserId);
+ }
+ return targetUserId;
}
boolean isSingleton(String componentProcessName, ApplicationInfo aInfo,
@@ -13479,12 +13618,12 @@
if (UserHandle.getAppId(aInfo.uid) >= Process.FIRST_APPLICATION_UID) {
if ((flags & ServiceInfo.FLAG_SINGLE_USER) != 0) {
if (ActivityManager.checkUidPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS,
+ INTERACT_ACROSS_USERS,
aInfo.uid) != PackageManager.PERMISSION_GRANTED) {
ComponentName comp = new ComponentName(aInfo.packageName, className);
String msg = "Permission Denial: Component " + comp.flattenToShortString()
+ " requests FLAG_SINGLE_USER, but app does not hold "
- + android.Manifest.permission.INTERACT_ACROSS_USERS;
+ + INTERACT_ACROSS_USERS;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
@@ -13801,7 +13940,7 @@
}
userId = this.handleIncomingUser(callingPid, callingUid, userId,
- true, true, "registerReceiver", callerPackage);
+ true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
List allSticky = null;
@@ -14046,7 +14185,7 @@
}
userId = handleIncomingUser(callingPid, callingUid, userId,
- true, false, "broadcast", callerPackage);
+ true, ALLOW_NON_FULL, "broadcast", callerPackage);
// Make sure that the user who is receiving this broadcast is started.
// If not, we will just skip it.
@@ -14510,8 +14649,8 @@
throw new IllegalArgumentException("File descriptors passed in Intent");
}
- userId = handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, true, false, "removeStickyBroadcast", null);
+ userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+ userId, true, ALLOW_NON_FULL, "removeStickyBroadcast", null);
synchronized(this) {
if (checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
@@ -14604,7 +14743,7 @@
int userId, String abiOverride) {
enforceNotIsolatedCaller("startInstrumentation");
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, false, true, "startInstrumentation", null);
+ userId, false, ALLOW_FULL_ONLY, "startInstrumentation", null);
// Refuse possible leaked file descriptors
if (arguments != null && arguments.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Bundle");
@@ -16742,7 +16881,7 @@
private ProcessRecord findProcessLocked(String process, int userId, String callName) {
userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
- userId, true, true, callName, null);
+ userId, true, ALLOW_FULL_ONLY, callName, null);
ProcessRecord proc = null;
try {
int pid = Integer.parseInt(process);
@@ -16859,6 +16998,17 @@
currentProfileIds[i] = profiles.get(i).id;
}
mCurrentProfileIds = currentProfileIds;
+
+ synchronized (mUserProfileGroupIdsSelfLocked) {
+ mUserProfileGroupIdsSelfLocked.clear();
+ final List<UserInfo> users = getUserManagerLocked().getUsers(false);
+ for (int i = 0; i < users.size(); i++) {
+ UserInfo user = users.get(i);
+ if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
+ mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
+ }
+ }
+ }
}
private Set getProfileIdsLocked(int userId) {
@@ -17031,7 +17181,7 @@
throws RemoteException {
}
}, 0, null, null,
- android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
+ INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
}
}
@@ -17350,7 +17500,7 @@
// Kick things off.
broadcastIntentLocked(null, null, stoppingIntent,
null, stoppingReceiver, 0, null, null,
- android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
+ INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
} finally {
Binder.restoreCallingIdentity(ident);
@@ -17401,14 +17551,14 @@
@Override
public UserInfo getCurrentUser() {
- if ((checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ if ((checkCallingPermission(INTERACT_ACROSS_USERS)
!= PackageManager.PERMISSION_GRANTED) && (
checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED)) {
String msg = "Permission Denial: getCurrentUser() from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
- + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS;
+ + " requires " + INTERACT_ACROSS_USERS;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
@@ -17423,12 +17573,12 @@
@Override
public boolean isUserRunning(int userId, boolean orStopped) {
- if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ if (checkCallingPermission(INTERACT_ACROSS_USERS)
!= PackageManager.PERMISSION_GRANTED) {
String msg = "Permission Denial: isUserRunning() from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
- + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS;
+ + " requires " + INTERACT_ACROSS_USERS;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
@@ -17451,12 +17601,12 @@
@Override
public int[] getRunningUserIds() {
- if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+ if (checkCallingPermission(INTERACT_ACROSS_USERS)
!= PackageManager.PERMISSION_GRANTED) {
String msg = "Permission Denial: isUserRunning() from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
- + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS;
+ + " requires " + INTERACT_ACROSS_USERS;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index d70e8b7..46521c5 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -16,7 +16,6 @@
package com.android.server.am;
-import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
import android.os.PersistableBundle;
import android.os.Trace;
@@ -498,7 +497,7 @@
}
}
- void setTask(TaskRecord newTask, boolean isRoot) {
+ void setTask(TaskRecord newTask, TaskRecord taskToAffiliateWith) {
if (task != null && task.removeActivity(this)) {
if (task != newTask) {
task.stack.removeTask(task);
@@ -508,6 +507,15 @@
}
}
task = newTask;
+ setTaskToAffiliateWith(taskToAffiliateWith);
+ }
+
+ void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) {
+ if (taskToAffiliateWith != null &&
+ launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE &&
+ launchMode != ActivityInfo.LAUNCH_SINGLE_TASK) {
+ task.setTaskToAffiliateWith(taskToAffiliateWith);
+ }
}
boolean changeWindowTranslucency(boolean toOpaque) {
@@ -1038,8 +1046,8 @@
return null;
}
- private static String createImageFilename(ActivityRecord r) {
- return String.valueOf(r.task.taskId) + ACTIVITY_ICON_SUFFIX + r.createTime +
+ private static String createImageFilename(ActivityRecord r, int taskId) {
+ return String.valueOf(taskId) + ACTIVITY_ICON_SUFFIX + r.createTime +
TaskPersister.IMAGE_EXTENSION;
}
@@ -1056,7 +1064,8 @@
out.attribute(null, ATTR_USERID, String.valueOf(userId));
if (taskDescription != null) {
- task.saveTaskDescription(taskDescription, createImageFilename(this), out);
+ task.saveTaskDescription(taskDescription, createImageFilename(this, task.taskId),
+ out);
}
out.startTag(null, TAG_INTENT);
@@ -1148,7 +1157,7 @@
r.persistentState = persistentState;
if (createTime >= 0) {
- taskDescription.setIcon(TaskPersister.restoreImage(createImageFilename(r)));
+ taskDescription.setIcon(TaskPersister.restoreImage(createImageFilename(r, taskId)));
}
r.taskDescription = taskDescription;
r.createTime = createTime;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index c6bba22..1f92bf9 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -765,8 +765,7 @@
return null;
}
- TaskRecord tr = who.task;
- if (mService.getMostRecentTask() != tr || isHomeStack()) {
+ if (isHomeStack()) {
// This is an optimization -- since we never show Home or Recents within Recents itself,
// we can just go ahead and skip taking the screenshot if this is the home stack. In
// the case where the most recent task is not the task that was supplied, then the stack
@@ -2138,7 +2137,7 @@
+ " Callers=" + Debug.getCallers(4));
if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p
+ " out to target's task " + target.task);
- p.setTask(targetTask, false);
+ p.setTask(targetTask, null);
targetTask.addActivityAtBottom(p);
mWindowManager.setAppGroupId(p.appToken, targetTaskId);
@@ -2269,7 +2268,7 @@
+ start + "-" + i + " to task=" + task + ":" + taskInsertionPoint);
for (int srcPos = start; srcPos >= i; --srcPos) {
final ActivityRecord p = activities.get(srcPos);
- p.setTask(task, false);
+ p.setTask(task, null);
task.addActivityAtIndex(taskInsertionPoint, p);
if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + p
@@ -3886,6 +3885,7 @@
// Task creator asked to remove this when done, or this task was a voice
// interaction, so it should not remain on the recent tasks list.
mService.mRecentTasks.remove(task);
+ task.closeRecentsChain();
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 65afd71..bc184c6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1531,11 +1531,12 @@
final Intent intent = r.intent;
final int callingUid = r.launchedFromUid;
- int launchFlags = intent.getFlags();
+ final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE;
+ final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK;
+ int launchFlags = intent.getFlags();
if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 &&
- (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE ||
- r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK)) {
+ (launchSingleInstance || launchSingleTask)) {
// We have a conflict between the Intent and the Activity manifest, manifest wins.
Slog.i(TAG, "Ignoring FLAG_ACTIVITY_NEW_DOCUMENT, launchMode is " +
"\"singleInstance\" or \"singleTask\"");
@@ -1556,6 +1557,9 @@
break;
}
}
+ final int launchBehindFlags = Intent.FLAG_ACTIVITY_LAUNCH_BEHIND |
+ Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+ final boolean affiliateTask = (launchFlags & launchBehindFlags) == launchBehindFlags;
if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
// For whatever reason this activity is being launched into a new
@@ -1617,8 +1621,7 @@
// instance... this new activity it is starting must go on its
// own task.
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
- } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
- || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
+ } else if (launchSingleInstance || launchSingleTask) {
// The activity being started is a single instance... it always
// gets launched into its own task.
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -1658,8 +1661,7 @@
ActivityStack targetStack;
if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
(launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
- || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
- || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
+ || launchSingleInstance || launchSingleTask) {
// If bring to front is requested, and no result is requested, and
// we can find a task that was started with this same
// component, then instead of launching bring that one to the front.
@@ -1668,9 +1670,8 @@
// a SINGLE_INSTANCE activity, there can be one and only one
// instance of it in the history, and it is always in its own
// unique task, so we do a special search.
- ActivityRecord intentActivity = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
- ? findTaskLocked(r)
- : findActivityLocked(intent, r.info);
+ ActivityRecord intentActivity = !launchSingleInstance ?
+ findTaskLocked(r) : findActivityLocked(intent, r.info);
if (intentActivity != null) {
if (isLockTaskModeViolation(intentActivity.task)) {
showLockTaskToast();
@@ -1708,6 +1709,9 @@
sourceStack.topActivity().task == sourceRecord.task)) {
// We really do want to push this one into the
// user's face, right now.
+ if (affiliateTask && sourceRecord != null) {
+ intentActivity.setTaskToAffiliateWith(sourceRecord.task);
+ }
movedHome = true;
targetStack.moveTaskToFrontLocked(intentActivity.task, r, options);
if ((launchFlags &
@@ -1746,8 +1750,7 @@
reuseTask.performClearTaskLocked();
reuseTask.setIntent(r.intent, r.info);
} else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
- || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
- || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
+ || launchSingleInstance || launchSingleTask) {
// In this situation we want to remove all activities
// from the task up to the one being started. In most
// cases this means we are resetting the task to its
@@ -1848,8 +1851,7 @@
if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
if (top.app != null && top.app.thread != null) {
if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
- || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP
- || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
+ || launchSingleInstance || launchSingleTask) {
ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top,
top.task);
// For paranoia, make sure we have correctly
@@ -1884,6 +1886,9 @@
boolean newTask = false;
boolean keepCurTransition = false;
+ TaskRecord taskToAffiliate = affiliateTask && sourceRecord != null ?
+ sourceRecord.task : null;
+
// Should this be considered a new task?
if (r.resultTo == null && !addingToTask
&& (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
@@ -1898,11 +1903,11 @@
r.setTask(targetStack.createTaskRecord(getNextTaskId(),
newTaskInfo != null ? newTaskInfo : r.info,
newTaskIntent != null ? newTaskIntent : intent,
- voiceSession, voiceInteractor, true), true);
+ voiceSession, voiceInteractor, true), taskToAffiliate);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
r.task);
} else {
- r.setTask(reuseTask, true);
+ r.setTask(reuseTask, taskToAffiliate);
}
if (!movedHome) {
if ((launchFlags &
@@ -1914,7 +1919,7 @@
}
}
} else if (sourceRecord != null) {
- TaskRecord sourceTask = sourceRecord.task;
+ final TaskRecord sourceTask = sourceRecord.task;
if (isLockTaskModeViolation(sourceTask)) {
Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
@@ -1922,8 +1927,7 @@
targetStack = sourceTask.stack;
targetStack.moveToFront();
mWindowManager.moveTaskToTop(targetStack.topTask().taskId);
- if (!addingToTask &&
- (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
+ if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
// In this case, we are adding the activity to an existing
// task, but the caller has asked to clear that task if the
// activity is already running.
@@ -1963,7 +1967,7 @@
// An existing activity is starting this new activity, so we want
// to keep the new one in the same task as the one that is starting
// it.
- r.setTask(sourceTask, false);
+ r.setTask(sourceTask, null);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+ " in existing task " + r.task + " from source " + sourceRecord);
@@ -1975,7 +1979,7 @@
targetStack.moveToFront();
ActivityRecord prev = targetStack.topActivity();
r.setTask(prev != null ? prev.task : targetStack.createTaskRecord(getNextTaskId(),
- r.info, intent, null, null, true), true);
+ r.info, intent, null, null, true), null);
mWindowManager.moveTaskToTop(r.task.taskId);
if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+ " in new guessed " + r.task);
@@ -3407,7 +3411,8 @@
public final int startActivity(Intent intent) {
mService.enforceNotIsolatedCaller("ActivityContainer.startActivity");
int userId = mService.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), mCurrentUser, false, true, "ActivityContainer", null);
+ Binder.getCallingUid(), mCurrentUser, false,
+ ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
// TODO: Switch to user app stacks here.
intent.addFlags(FORCE_NEW_TASK_FLAGS);
String mimeType = intent.getType();
@@ -3433,7 +3438,8 @@
private void checkEmbeddedAllowedInner(Intent intent, String resolvedType) {
int userId = mService.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), mCurrentUser, false, true, "ActivityContainer", null);
+ Binder.getCallingUid(), mCurrentUser, false,
+ ActivityManagerService.ALLOW_FULL_ONLY, "ActivityContainer", null);
if (resolvedType == null) {
resolvedType = intent.getType();
if (resolvedType == null && intent.getData() != null
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 1982d7e..38eef208 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,7 +16,6 @@
package com.android.server.am;
-import android.app.ActivityManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Debug;
@@ -151,6 +150,20 @@
}
}
+ private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) {
+ if (taskId < 0) {
+ return null;
+ }
+ for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+ final TaskRecord task = tasks.get(taskNdx);
+ if (task.taskId == taskId) {
+ return task;
+ }
+ }
+ Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
+ return null;
+ }
+
ArrayList<TaskRecord> restoreTasksLocked() {
final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
@@ -201,7 +214,7 @@
XmlUtils.skipCurrentTag(in);
}
} catch (Exception e) {
- Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error " + e);
+ Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
Slog.e(TAG, "Failing file: " + fileToString(taskFile));
deleteFile = true;
} finally {
@@ -222,6 +235,13 @@
removeObsoleteFiles(recoveredTaskIds);
}
+ // Fixup task affiliation from taskIds
+ for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+ final TaskRecord task = tasks.get(taskNdx);
+ task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
+ task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
+ }
+
TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
tasks.toArray(tasksArray);
Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index c9890cd3cd..76373df 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -64,6 +64,9 @@
private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
+ private static final String ATTR_TASK_AFFILIATION = "task_affiliation";
+ private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
+ private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
private static final String LAST_ACTIVITY_ICON_SUFFIX = "_last_activity_icon_";
private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
@@ -132,14 +135,22 @@
private final String mFilename;
CharSequence lastDescription; // Last description captured for this item.
+ int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
+ TaskRecord mPrevAffiliate; // previous task in affiliated chain.
+ int mPrevAffiliateTaskId = -1; // previous id for persistence.
+ TaskRecord mNextAffiliate; // next task in affiliated chain.
+ int mNextAffiliateTaskId = -1; // next id for persistence.
+
final ActivityManagerService mService;
TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
mService = service;
- mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION;
+ mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
+ TaskPersister.IMAGE_EXTENSION;
mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
taskId = _taskId;
+ mAffiliatedTaskId = _taskId;
voiceSession = _voiceSession;
voiceInteractor = _voiceInteractor;
mActivities = new ArrayList<ActivityRecord>();
@@ -151,9 +162,11 @@
boolean _rootWasReset, boolean _askedCompatMode, int _taskType, int _userId,
String _lastDescription, ArrayList<ActivityRecord> activities, long _firstActiveTime,
long _lastActiveTime, long lastTimeMoved, boolean neverRelinquishIdentity,
- ActivityManager.TaskDescription _lastTaskDescription) {
+ ActivityManager.TaskDescription _lastTaskDescription, int taskAffiliation,
+ int prevTaskId, int nextTaskId) {
mService = service;
- mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX + TaskPersister.IMAGE_EXTENSION;
+ mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
+ TaskPersister.IMAGE_EXTENSION;
mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
taskId = _taskId;
intent = _intent;
@@ -175,6 +188,9 @@
mLastTimeMoved = lastTimeMoved;
mNeverRelinquishIdentity = neverRelinquishIdentity;
lastTaskDescription = _lastTaskDescription;
+ mAffiliatedTaskId = taskAffiliation;
+ mPrevAffiliateTaskId = prevTaskId;
+ mNextAffiliateTaskId = nextTaskId;
}
void touchActiveTime() {
@@ -257,6 +273,50 @@
return mTaskToReturnTo;
}
+ void setPrevAffiliate(TaskRecord prevAffiliate) {
+ mPrevAffiliate = prevAffiliate;
+ mPrevAffiliateTaskId = prevAffiliate == null ? -1 : prevAffiliate.taskId;
+ }
+
+ void setNextAffiliate(TaskRecord nextAffiliate) {
+ mNextAffiliate = nextAffiliate;
+ mNextAffiliateTaskId = nextAffiliate == null ? -1 : nextAffiliate.taskId;
+ }
+
+ // Close up recents linked list.
+ void closeRecentsChain() {
+ if (mPrevAffiliate != null) {
+ mPrevAffiliate.setNextAffiliate(mNextAffiliate);
+ }
+ if (mNextAffiliate != null) {
+ mNextAffiliate.setPrevAffiliate(mPrevAffiliate);
+ }
+ setPrevAffiliate(null);
+ setNextAffiliate(null);
+ }
+
+ void setTaskToAffiliateWith(TaskRecord taskToAffiliateWith) {
+ closeRecentsChain();
+ mAffiliatedTaskId = taskToAffiliateWith.mAffiliatedTaskId;
+ // Find the end
+ while (taskToAffiliateWith.mNextAffiliate != null) {
+ final TaskRecord nextRecents = taskToAffiliateWith.mNextAffiliate;
+ if (nextRecents.mAffiliatedTaskId != mAffiliatedTaskId) {
+ Slog.e(TAG, "setTaskToAffiliateWith: nextRecents=" + nextRecents + " affilTaskId="
+ + nextRecents.mAffiliatedTaskId + " should be " + mAffiliatedTaskId);
+ if (nextRecents.mPrevAffiliate == taskToAffiliateWith) {
+ nextRecents.setPrevAffiliate(null);
+ }
+ taskToAffiliateWith.setNextAffiliate(null);
+ break;
+ }
+ taskToAffiliateWith = nextRecents;
+ }
+ taskToAffiliateWith.setNextAffiliate(this);
+ setPrevAffiliate(taskToAffiliateWith);
+ setNextAffiliate(null);
+ }
+
void setLastThumbnail(Bitmap thumbnail) {
mLastThumbnail = thumbnail;
if (thumbnail == null) {
@@ -681,11 +741,13 @@
if (lastDescription != null) {
out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
}
-
if (lastTaskDescription != null) {
saveTaskDescription(lastTaskDescription, String.valueOf(taskId) +
LAST_ACTIVITY_ICON_SUFFIX + lastActiveTime, out);
}
+ out.attribute(null, ATTR_TASK_AFFILIATION, String.valueOf(mAffiliatedTaskId));
+ out.attribute(null, ATTR_PREV_AFFILIATION, String.valueOf(mPrevAffiliateTaskId));
+ out.attribute(null, ATTR_NEXT_AFFILIATION, String.valueOf(mNextAffiliateTaskId));
if (affinityIntent != null) {
out.startTag(null, TAG_AFFINITYINTENT);
@@ -733,6 +795,9 @@
int taskId = -1;
final int outerDepth = in.getDepth();
ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription();
+ int taskAffiliation = -1;
+ int prevTaskId = -1;
+ int nextTaskId = -1;
for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
final String attrName = in.getAttributeName(attrNdx);
@@ -767,6 +832,12 @@
neverRelinquishIdentity = Boolean.valueOf(attrValue);
} else if (readTaskDescriptionAttribute(taskDescription, attrName, attrValue)) {
// Completed in TaskPersister.readTaskDescriptionAttribute()
+ } else if (ATTR_TASK_AFFILIATION.equals(attrName)) {
+ taskAffiliation = Integer.valueOf(attrValue);
+ } else if (ATTR_PREV_AFFILIATION.equals(attrName)) {
+ prevTaskId = Integer.valueOf(attrValue);
+ } else if (ATTR_NEXT_AFFILIATION.equals(attrName)) {
+ nextTaskId = Integer.valueOf(attrValue);
} else {
Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
}
@@ -806,7 +877,8 @@
final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
affinityIntent, affinity, realActivity, origActivity, rootHasReset,
askedCompatMode, taskType, userId, lastDescription, activities, firstActiveTime,
- lastActiveTime, lastTimeOnTop, neverRelinquishIdentity, taskDescription);
+ lastActiveTime, lastTimeOnTop, neverRelinquishIdentity, taskDescription,
+ taskAffiliation, prevTaskId, nextTaskId);
for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
activities.get(activityNdx).task = task;
@@ -867,6 +939,10 @@
pw.print(" lastActiveTime="); pw.print(lastActiveTime);
pw.print(" (inactive for ");
pw.print((getInactiveDuration()/1000)); pw.println("s)");
+ pw.print(prefix); pw.print("isPersistable="); pw.print(isPersistable);
+ pw.print(" affiliation="); pw.print(mAffiliatedTaskId);
+ pw.print(" prevAffiliation="); pw.print(mPrevAffiliateTaskId);
+ pw.print(" nextAffiliation="); pw.println(mNextAffiliateTaskId);
}
@Override
diff --git a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
index 2c8c1c1..432424b 100644
--- a/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
+++ b/services/core/java/com/android/server/hdmi/ActiveSourceHandler.java
@@ -66,7 +66,7 @@
}
HdmiCecDeviceInfo device = mService.getDeviceInfo(activeAddress);
if (device == null) {
- tv.addAndStartAction(new NewDeviceAction(tv, activeAddress, activePath));
+ tv.startNewDeviceAction(activeAddress, activePath);
}
int currentActive = tv.getActiveSource();
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 1cdf44c..3028100 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -203,5 +203,8 @@
// in config.xml to allow customization.
static final int IRT_MS = 300;
+ static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "hdmi_cec.preferred_address.playback";
+ static final String PROPERTY_PREFERRED_ADDRESS_TV = "hdmi_cec.preferred_address.tv";
+
private Constants() { /* cannot be instantiated */ }
}
diff --git a/services/core/java/com/android/server/hdmi/FeatureAction.java b/services/core/java/com/android/server/hdmi/FeatureAction.java
index 7d15f4c..b7b2f90 100644
--- a/services/core/java/com/android/server/hdmi/FeatureAction.java
+++ b/services/core/java/com/android/server/hdmi/FeatureAction.java
@@ -18,11 +18,13 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.hdmi.HdmiControlService.DevicePollingCallback;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -59,6 +61,8 @@
// Timer that manages timeout events.
protected ActionTimer mActionTimer;
+ private ArrayList<Pair<FeatureAction, Runnable>> mOnFinishedCallbacks;
+
FeatureAction(HdmiCecLocalDevice source) {
mSource = source;
mService = mSource.getService();
@@ -220,8 +224,22 @@
* Finish up the action. Reset the state, and remove itself from the action queue.
*/
protected void finish() {
+ finish(true);
+ }
+
+ void finish(boolean removeSelf) {
clear();
- removeAction(this);
+ if (removeSelf) {
+ removeAction(this);
+ }
+ if (mOnFinishedCallbacks != null) {
+ for (Pair<FeatureAction, Runnable> actionCallbackPair: mOnFinishedCallbacks) {
+ if (actionCallbackPair.first.mState != STATE_NONE) {
+ actionCallbackPair.second.run();
+ }
+ }
+ mOnFinishedCallbacks = null;
+ }
}
protected final HdmiCecLocalDevice localDevice() {
@@ -244,10 +262,17 @@
return mSource.getDeviceInfo().getPhysicalAddress();
}
- protected void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
+ protected final void sendUserControlPressedAndReleased(int targetAddress, int uiCommand) {
sendCommand(HdmiCecMessageBuilder.buildUserControlPressed(
getSourceAddress(), targetAddress, uiCommand));
sendCommand(HdmiCecMessageBuilder.buildUserControlReleased(
getSourceAddress(), targetAddress));
}
+
+ protected final void addOnFinishedCallback(FeatureAction action, Runnable runnable) {
+ if (mOnFinishedCallbacks == null) {
+ mOnFinishedCallbacks = new ArrayList<>();
+ }
+ mOnFinishedCallbacks.add(Pair.create(action, runnable));
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 14d9b75..72519f2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -265,8 +265,6 @@
@ServiceThreadOnly
void clearLogicalAddress() {
assertRunOnServiceThread();
- // TODO: consider to backup logical address so that new logical address
- // allocation can use it as preferred address.
for (int i = 0; i < mLocalDevices.size(); ++i) {
mLocalDevices.valueAt(i).clearAddress();
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 3937ce1..f1e7ff2 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -20,6 +20,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.SystemProperties;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -65,7 +66,7 @@
// Note that access to this collection should happen in service thread.
private final LinkedList<FeatureAction> mActions = new LinkedList<>();
- private Handler mHandler = new Handler () {
+ private final Handler mHandler = new Handler () {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -108,8 +109,7 @@
@ServiceThreadOnly
void init() {
assertRunOnServiceThread();
- mPreferredAddress = Constants.ADDR_UNREGISTERED;
- // TODO: load preferred address from permanent storage.
+ mPreferredAddress = getPreferredAddress();
}
/**
@@ -118,6 +118,16 @@
protected abstract void onAddressAllocated(int logicalAddress, boolean fromBootup);
/**
+ * Get the preferred logical address from system properties.
+ */
+ protected abstract int getPreferredAddress();
+
+ /**
+ * Set the preferred logical address to system properties.
+ */
+ protected abstract void setPreferredAddress(int addr);
+
+ /**
* Dispatch incoming message.
*
* @param message incoming message
@@ -398,6 +408,7 @@
assertRunOnServiceThread();
mAddress = mPreferredAddress = logicalAddress;
onAddressAllocated(logicalAddress, fromBootup);
+ setPreferredAddress(logicalAddress);
}
@ServiceThreadOnly
@@ -427,18 +438,6 @@
}
@ServiceThreadOnly
- void setPreferredAddress(int addr) {
- assertRunOnServiceThread();
- mPreferredAddress = addr;
- }
-
- @ServiceThreadOnly
- int getPreferredAddress() {
- assertRunOnServiceThread();
- return mPreferredAddress;
- }
-
- @ServiceThreadOnly
void addAndStartAction(final FeatureAction action) {
assertRunOnServiceThread();
if (mService.isPowerStandbyOrTransient()) {
@@ -482,6 +481,7 @@
@ServiceThreadOnly
void removeAction(final FeatureAction action) {
assertRunOnServiceThread();
+ action.finish(false);
mActions.remove(action);
checkIfPendingActionsCleared();
}
@@ -502,8 +502,8 @@
while (iter.hasNext()) {
FeatureAction action = iter.next();
if (action != exception && action.getClass().equals(clazz)) {
- action.clear();
- mActions.remove(action);
+ action.finish(false);
+ iter.remove();
}
}
checkIfPendingActionsCleared();
@@ -610,7 +610,7 @@
* Called when the system goes to standby mode.
*
* @param initiatedByCec true if this power sequence is initiated
- * by the reception the CEC messages like <Standby>
+ * by the reception the CEC messages like <Standby>
*/
protected void onStandby(boolean initiatedByCec) {}
@@ -619,11 +619,19 @@
* actions are completed or timeout is issued.
*
* @param initiatedByCec true if this sequence is initiated
- * by the reception the CEC messages like <Standby>
- * @param callback callback interface to get notified when all pending actions are cleared
+ * by the reception the CEC messages like <Standby>
+ * @param origialCallback callback interface to get notified when all pending actions are
+ * cleared
*/
- protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
- mPendingActionClearedCallback = callback;
+ protected void disableDevice(boolean initiatedByCec,
+ final PendingActionClearedCallback origialCallback) {
+ mPendingActionClearedCallback = new PendingActionClearedCallback() {
+ @Override
+ public void onCleared(HdmiCecLocalDevice device) {
+ mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT);
+ origialCallback.onCleared(device);
+ }
+ };
mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT),
DEVICE_CLEANUP_TIMEOUT);
}
@@ -637,7 +645,7 @@
Iterator<FeatureAction> iter = mActions.iterator();
while (iter.hasNext()) {
FeatureAction action = iter.next();
- action.finish();
+ action.finish(false);
iter.remove();
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 06907ce..a66d78c 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -20,6 +20,7 @@
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.util.Slog;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
@@ -44,6 +45,22 @@
mAddress, mService.getPhysicalAddress(), mDeviceType));
}
+ @Override
+ @ServiceThreadOnly
+ protected int getPreferredAddress() {
+ assertRunOnServiceThread();
+ return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
+ Constants.ADDR_UNREGISTERED);
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected void setPreferredAddress(int addr) {
+ assertRunOnServiceThread();
+ SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_PLAYBACK,
+ String.valueOf(addr));
+ }
+
@ServiceThreadOnly
void oneTouchPlay(IHdmiControlCallback callback) {
assertRunOnServiceThread();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 923b87d..44b0f28eb 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -20,11 +20,13 @@
import android.hardware.hdmi.HdmiCecDeviceInfo;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.IHdmiControlCallback;
+import android.media.AudioManager;
import android.media.AudioManager.OnAudioPortUpdateListener;
import android.media.AudioPatch;
import android.media.AudioPort;
import android.media.AudioSystem;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.util.Slog;
@@ -107,6 +109,22 @@
// TODO: unregister audio port update listener if local device is released.
}
+ @Override
+ @ServiceThreadOnly
+ protected int getPreferredAddress() {
+ assertRunOnServiceThread();
+ return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_TV,
+ Constants.ADDR_UNREGISTERED);
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected void setPreferredAddress(int addr) {
+ assertRunOnServiceThread();
+ SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_TV,
+ String.valueOf(addr));
+ }
+
private void registerAudioPortUpdateListener() {
mService.getAudioManager().registerAudioPortUpdateListener(
new OnAudioPortUpdateListener() {
@@ -144,6 +162,11 @@
handleSelectInternalSource(callback);
return;
}
+ if (!mService.isControlEnabled()) {
+ setActiveSource(targetAddress);
+ invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+ return;
+ }
HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress);
if (targetDevice == null) {
invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
@@ -222,18 +245,23 @@
void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
assertRunOnServiceThread();
// Seq #20
- if (!mService.isControlEnabled() || portId == getActivePortId()) {
+ if (!mService.isValidPortId(portId)) {
invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
return;
}
- // TODO: Make sure this call does not stem from <Active Source> message reception.
-
+ if (!mService.isControlEnabled()) {
+ setActivePortId(portId);
+ invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+ return;
+ }
+ if (portId == getActivePortId()) {
+ invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
+ return;
+ }
setActivePortId(portId);
// TODO: Return immediately if the operation is triggered by <Text/Image View On>
- // and this is the first notification about the active input after power-on.
- // TODO: Handle invalid port id / active input which should be treated as an
- // internal tuner.
-
+ // and this is the first notification about the active input after power-on
+ // (switch to HDMI didn't happen so far but is expected to happen soon).
removeAction(RoutingControlAction.class);
int oldPath = mService.portIdToPath(mService.portIdToPath(getActivePortId()));
@@ -371,10 +399,28 @@
if (!isInDeviceList(path, address)) {
handleNewDeviceAtTheTailOfActivePath(path);
}
- addAndStartAction(new NewDeviceAction(this, address, path));
+ startNewDeviceAction(address, path);
return true;
}
+ void startNewDeviceAction(int address, int path) {
+ for (NewDeviceAction action : getActions(NewDeviceAction.class)) {
+ // If there is new device action which has the same logical address and path
+ // ignore new request.
+ // NewDeviceAction is created whenever it receives <Report Physical Address>.
+ // And there is a chance starting NewDeviceAction for the same source.
+ // Usually, new device sends <Report Physical Address> when it's plugged
+ // in. However, TV can detect a new device from HotPlugDetectionAction,
+ // which sends <Give Physical Address> to the source for newly detected
+ // device.
+ if (action.isActionOf(address, path)) {
+ return;
+ }
+ }
+
+ addAndStartAction(new NewDeviceAction(this, address, path));
+ }
+
private void handleNewDeviceAtTheTailOfActivePath(int path) {
// Seq #22
if (isTailOfActivePath(path, getActivePath())) {
@@ -536,10 +582,17 @@
}
@ServiceThreadOnly
+ // Seq #32
void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {
assertRunOnServiceThread();
+ if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) {
+ setSystemAudioMode(false, true);
+ invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+ return;
+ }
HdmiCecDeviceInfo avr = getAvrDeviceInfo();
if (avr == null) {
+ setSystemAudioMode(false, true);
invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
return;
}
@@ -654,8 +707,10 @@
synchronized (mLock) {
mSystemAudioMute = mute;
mSystemAudioVolume = volume;
- // TODO: pass volume to service (audio service) after scale it to local volume level.
- mService.setAudioStatus(mute, volume);
+ int maxVolume = mService.getAudioManager().getStreamMaxVolume(
+ AudioManager.STREAM_MUSIC);
+ mService.setAudioStatus(mute,
+ VolumeControlAction.scaleToCustomVolume(volume, maxVolume));
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index a2a795c..a9a391b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -398,6 +398,15 @@
return Constants.INVALID_PORT_ID;
}
+ boolean isValidPortId(int portId) {
+ for (HdmiPortInfo info : mPortInfo) {
+ if (portId == info.getId()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Returns {@link Looper} for IO operation.
*
@@ -577,7 +586,22 @@
}
void setAudioStatus(boolean mute, int volume) {
- // TODO: Hook up with AudioManager.
+ AudioManager audioManager = getAudioManager();
+ boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
+ if (mute) {
+ if (!muted) {
+ audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
+ }
+ } else {
+ if (muted) {
+ audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
+ }
+ // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
+ // volume change notification back to hdmi control service.
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
+ AudioManager.FLAG_SHOW_UI |
+ AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
+ }
}
void announceSystemAudioModeChange(boolean enabled) {
diff --git a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
index 185de59f..6204c16 100644
--- a/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
+++ b/services/core/java/com/android/server/hdmi/HotplugDetectionAction.java
@@ -219,7 +219,7 @@
return;
}
- // Should ave only one Device Select Action
+ // Should have only one Device Select Action
DeviceSelectAction action = actions.get(0);
if (action.getTargetAddress() == address) {
removeAction(DeviceSelectAction.class);
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index 80deaab..f89e299 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -56,7 +56,6 @@
* @param source {@link HdmiCecLocalDevice} instance
* @param deviceLogicalAddress logical address of the device in interest
* @param devicePhysicalAddress physical address of the device in interest
- * @param requireRoutingChange whether to initiate routing change or not
*/
NewDeviceAction(HdmiCecLocalDevice source, int deviceLogicalAddress,
int devicePhysicalAddress) {
@@ -68,18 +67,6 @@
@Override
public boolean start() {
- if (HdmiUtils.getTypeFromAddress(getSourceAddress())
- == HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) {
- if (tv().getAvrDeviceInfo() == null) {
- // TODO: Start system audio initiation action
- }
-
- if (shouldTryArcInitiation()) {
- addAndStartAction(new RequestArcInitiationAction(localDevice(),
- mDeviceLogicalAddress));
- }
- }
-
mState = STATE_WAITING_FOR_SET_OSD_NAME;
if (mayProcessCommandIfCached(mDeviceLogicalAddress, Constants.MESSAGE_SET_OSD_NAME)) {
return true;
@@ -91,10 +78,6 @@
return true;
}
- private boolean shouldTryArcInitiation() {
- return tv().isConnectedToArcPort(mDevicePhysicalAddress) && tv().isArcFeatureEnabled();
- }
-
@Override
public boolean processCommand(HdmiCecMessage cmd) {
// For the logical device in interest, we want two more pieces of information -
@@ -172,6 +155,23 @@
mDeviceLogicalAddress, mDevicePhysicalAddress,
HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress),
mVendorId, mDisplayName));
+
+ if (HdmiUtils.getTypeFromAddress(mDeviceLogicalAddress)
+ == HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) {
+ if (tv().getSystemAudioMode()) {
+ addAndStartAction(new SystemAudioAutoInitiationAction(localDevice(),
+ mDeviceLogicalAddress));
+ }
+
+ if (shouldTryArcInitiation()) {
+ addAndStartAction(new RequestArcInitiationAction(localDevice(),
+ mDeviceLogicalAddress));
+ }
+ }
+ }
+
+ private boolean shouldTryArcInitiation() {
+ return tv().isConnectedToArcPort(mDevicePhysicalAddress) && tv().isArcFeatureEnabled();
}
@Override
@@ -188,4 +188,8 @@
finish();
}
}
+
+ boolean isActionOf(int address, int path) {
+ return (mDeviceLogicalAddress == address) && (mDevicePhysicalAddress == path);
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index 86895cc..4be036a 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -23,14 +23,20 @@
import android.os.RemoteException;
import android.util.Slog;
+import java.util.List;
+
/**
* Base feature action class for SystemAudioActionFromTv and SystemAudioActionFromAvr.
*/
abstract class SystemAudioAction extends FeatureAction {
private static final String TAG = "SystemAudioAction";
+ // Transient state to differentiate with STATE_NONE where the on-finished callback
+ // will not be called.
+ private static final int STATE_CHECK_ROUTING_IN_PRGRESS = 1;
+
// State in which waits for <SetSystemAudioMode>.
- private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 1;
+ private static final int STATE_WAIT_FOR_SET_SYSTEM_AUDIO_MODE = 2;
private static final int MAX_SEND_RETRY_COUNT = 2;
@@ -65,7 +71,25 @@
mCallback = callback;
}
+ // Seq #27
protected void sendSystemAudioModeRequest() {
+ mState = STATE_CHECK_ROUTING_IN_PRGRESS;
+ List<RoutingControlAction> routingActions = getActions(RoutingControlAction.class);
+ if (!routingActions.isEmpty()) {
+ // Should have only one Routing Control Action
+ RoutingControlAction routingAction = routingActions.get(0);
+ routingAction.addOnFinishedCallback(this, new Runnable() {
+ @Override
+ public void run() {
+ sendSystemAudioModeRequestInternal();
+ }
+ });
+ return;
+ }
+ sendSystemAudioModeRequestInternal();
+ }
+
+ private void sendSystemAudioModeRequestInternal() {
int avrPhysicalAddress = tv().getAvrDeviceInfo().getPhysicalAddress();
HdmiCecMessage command = HdmiCecMessageBuilder.buildSystemAudioModeRequest(
getSourceAddress(),
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index a565077..77783b7 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -23,7 +23,7 @@
/**
* Feature action that handles System Audio initiated by AVR devices.
*/
-// # Seq 33
+// Seq #33
final class SystemAudioActionFromAvr extends SystemAudioAction {
/**
* Constructor
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
index 2146c4e..cb3588c 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
@@ -24,6 +24,7 @@
* Feature action that handles System Audio initiated by TV devices.
*/
final class SystemAudioActionFromTv extends SystemAudioAction {
+
/**
* Constructor
*
@@ -41,9 +42,6 @@
@Override
boolean start() {
- // TODO: Check HDMI-CEC is enabled.
- // TODO: Move to the waiting state if currently a routing change is in progress.
-
removeSystemAudioActionInProgress();
sendSystemAudioModeRequest();
return true;
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
index 80954d4..d03634e 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAutoInitiationAction.java
@@ -21,6 +21,7 @@
/**
* Action to initiate system audio once AVR is detected on Device discovery action.
*/
+// Seq #27
final class SystemAudioAutoInitiationAction extends FeatureAction {
private final int mAvrAddress;
diff --git a/services/core/java/com/android/server/location/ActivityRecognitionProxy.java b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
new file mode 100644
index 0000000..7e7f2e4
--- /dev/null
+++ b/services/core/java/com/android/server/location/ActivityRecognitionProxy.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import com.android.server.ServiceWatcher;
+
+import android.content.Context;
+import android.hardware.location.ActivityRecognitionHardware;
+import android.hardware.location.IActivityRecognitionHardwareWatcher;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Proxy class to bind GmsCore to the ActivityRecognitionHardware.
+ *
+ * @hide
+ */
+public class ActivityRecognitionProxy {
+ private final String TAG = "ActivityRecognitionProxy";
+ private final ServiceWatcher mServiceWatcher;
+ private final ActivityRecognitionHardware mActivityRecognitionHardware;
+
+ private ActivityRecognitionProxy(
+ Context context,
+ Handler handler,
+ ActivityRecognitionHardware activityRecognitionHardware,
+ int overlaySwitchResId,
+ int defaultServicePackageNameResId,
+ int initialPackageNameResId) {
+ mActivityRecognitionHardware = activityRecognitionHardware;
+
+ Runnable newServiceWork = new Runnable() {
+ @Override
+ public void run() {
+ bindProvider(mActivityRecognitionHardware);
+ }
+ };
+
+ // prepare the connection to the provider
+ mServiceWatcher = new ServiceWatcher(
+ context,
+ TAG,
+ "com.android.location.service.ActivityRecognitionProvider",
+ overlaySwitchResId,
+ defaultServicePackageNameResId,
+ initialPackageNameResId,
+ newServiceWork,
+ handler);
+ }
+
+ /**
+ * Creates an instance of the proxy and binds it to the appropriate FusedProvider.
+ *
+ * @return An instance of the proxy if it could be bound, null otherwise.
+ */
+ public static ActivityRecognitionProxy createAndBind(
+ Context context,
+ Handler handler,
+ ActivityRecognitionHardware activityRecognitionHardware,
+ int overlaySwitchResId,
+ int defaultServicePackageNameResId,
+ int initialPackageNameResId) {
+ ActivityRecognitionProxy activityRecognitionProxy = new ActivityRecognitionProxy(
+ context,
+ handler,
+ activityRecognitionHardware,
+ overlaySwitchResId,
+ defaultServicePackageNameResId,
+ initialPackageNameResId);
+
+ // try to bind the provider
+ if (!activityRecognitionProxy.mServiceWatcher.start()) {
+ return null;
+ }
+
+ return activityRecognitionProxy;
+ }
+
+ /**
+ * Helper function to bind the FusedLocationHardware to the appropriate FusedProvider instance.
+ */
+ private void bindProvider(ActivityRecognitionHardware activityRecognitionHardware) {
+ IActivityRecognitionHardwareWatcher watcher =
+ IActivityRecognitionHardwareWatcher.Stub.asInterface(mServiceWatcher.getBinder());
+ if (watcher == null) {
+ Log.e(TAG, "No provider instance found on connection.");
+ return;
+ }
+
+ try {
+ watcher.onInstanceChanged(mActivityRecognitionHardware);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delivering hardware interface.", e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java
index c5b6c7b..8222155 100644
--- a/services/core/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/core/java/com/android/server/location/GpsLocationProvider.java
@@ -16,6 +16,15 @@
package com.android.server.location;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.location.GpsNetInitiatedHandler;
+import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+
import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.app.PendingIntent;
@@ -28,6 +37,7 @@
import android.hardware.location.GeofenceHardwareImpl;
import android.location.Criteria;
import android.location.FusedBatchOptions;
+import android.location.GpsMeasurementsEvent;
import android.location.IGpsGeofenceHardware;
import android.location.IGpsStatusListener;
import android.location.IGpsStatusProvider;
@@ -46,7 +56,6 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -64,27 +73,17 @@
import android.util.Log;
import android.util.NtpTrustedTime;
-import com.android.internal.app.IAppOpsService;
-import com.android.internal.app.IBatteryStats;
-import com.android.internal.location.GpsNetInitiatedHandler;
-import com.android.internal.location.ProviderProperties;
-import com.android.internal.location.ProviderRequest;
-import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
-import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
-
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
-import java.util.ArrayList;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.Date;
import java.util.Map.Entry;
import java.util.Properties;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
/**
* A GPS implementation of LocationProvider used by LocationManager.
@@ -314,7 +313,12 @@
private final ILocationManager mILocationManager;
private Location mLocation = new Location(LocationManager.GPS_PROVIDER);
private Bundle mLocationExtras = new Bundle();
- private ArrayList<Listener> mListeners = new ArrayList<Listener>();
+ private GpsStatusListenerHelper mListenerHelper = new GpsStatusListenerHelper() {
+ @Override
+ protected boolean isSupported() {
+ return native_is_measurement_supported();
+ }
+ };
// Handler for processing events
private Handler mHandler;
@@ -348,49 +352,29 @@
private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() {
@Override
public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException {
- if (listener == null) {
- throw new NullPointerException("listener is null in addGpsStatusListener");
- }
-
- synchronized (mListeners) {
- IBinder binder = listener.asBinder();
- int size = mListeners.size();
- for (int i = 0; i < size; i++) {
- Listener test = mListeners.get(i);
- if (binder.equals(test.mListener.asBinder())) {
- // listener already added
- return;
- }
- }
-
- Listener l = new Listener(listener);
- binder.linkToDeath(l, 0);
- mListeners.add(l);
- }
+ mListenerHelper.addListener(listener);
}
@Override
public void removeGpsStatusListener(IGpsStatusListener listener) {
- if (listener == null) {
- throw new NullPointerException("listener is null in addGpsStatusListener");
- }
+ mListenerHelper.removeListener(listener);
+ }
+ };
- synchronized (mListeners) {
- IBinder binder = listener.asBinder();
- Listener l = null;
- int size = mListeners.size();
- for (int i = 0; i < size && l == null; i++) {
- Listener test = mListeners.get(i);
- if (binder.equals(test.mListener.asBinder())) {
- l = test;
- }
- }
+ private final GpsMeasurementsProvider mGpsMeasurementsProvider = new GpsMeasurementsProvider() {
+ @Override
+ public boolean isSupported() {
+ return native_is_measurement_supported();
+ }
- if (l != null) {
- mListeners.remove(l);
- binder.unlinkToDeath(l, 0);
- }
- }
+ @Override
+ protected void onFirstListenerAdded() {
+ native_start_measurement_collection();
+ }
+
+ @Override
+ protected void onLastListenerRemoved() {
+ native_stop_measurement_collection();
}
};
@@ -402,6 +386,10 @@
return mGpsGeofenceBinder;
}
+ public GpsMeasurementsProvider getGpsMeasurementsProvider() {
+ return mGpsMeasurementsProvider;
+ }
+
private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
@@ -892,26 +880,6 @@
}
}
- private final class Listener implements IBinder.DeathRecipient {
- final IGpsStatusListener mListener;
-
- Listener(IGpsStatusListener listener) {
- mListener = listener;
- }
-
- @Override
- public void binderDied() {
- if (DEBUG) Log.d(TAG, "GPS status listener died");
-
- synchronized (mListeners) {
- mListeners.remove(this);
- }
- if (mListener != null) {
- mListener.asBinder().unlinkToDeath(this, 0);
- }
- }
- }
-
private void updateClientUids(WorkSource source) {
// Update work source.
WorkSource[] changes = mClientSource.setReturningDiffs(source);
@@ -1185,20 +1153,7 @@
if (DEBUG) Log.d(TAG, "TTFF: " + mTimeToFirstFix);
// notify status listeners
- synchronized (mListeners) {
- int size = mListeners.size();
- for (int i = 0; i < size; i++) {
- Listener listener = mListeners.get(i);
- try {
- listener.mListener.onFirstFix(mTimeToFirstFix);
- } catch (RemoteException e) {
- Log.w(TAG, "RemoteException in stopNavigating");
- mListeners.remove(listener);
- // adjust for size of list changing
- size--;
- }
- }
- }
+ mListenerHelper.onFirstFix(mTimeToFirstFix);
}
if (mSingleShot) {
@@ -1232,49 +1187,31 @@
private void reportStatus(int status) {
if (DEBUG) Log.v(TAG, "reportStatus status: " + status);
- synchronized (mListeners) {
- boolean wasNavigating = mNavigating;
+ boolean wasNavigating = mNavigating;
+ switch (status) {
+ case GPS_STATUS_SESSION_BEGIN:
+ mNavigating = true;
+ mEngineOn = true;
+ break;
+ case GPS_STATUS_SESSION_END:
+ mNavigating = false;
+ break;
+ case GPS_STATUS_ENGINE_ON:
+ mEngineOn = true;
+ break;
+ case GPS_STATUS_ENGINE_OFF:
+ mEngineOn = false;
+ mNavigating = false;
+ break;
+ }
- switch (status) {
- case GPS_STATUS_SESSION_BEGIN:
- mNavigating = true;
- mEngineOn = true;
- break;
- case GPS_STATUS_SESSION_END:
- mNavigating = false;
- break;
- case GPS_STATUS_ENGINE_ON:
- mEngineOn = true;
- break;
- case GPS_STATUS_ENGINE_OFF:
- mEngineOn = false;
- mNavigating = false;
- break;
- }
+ if (wasNavigating != mNavigating) {
+ mListenerHelper.onStatusChanged(mNavigating);
- if (wasNavigating != mNavigating) {
- int size = mListeners.size();
- for (int i = 0; i < size; i++) {
- Listener listener = mListeners.get(i);
- try {
- if (mNavigating) {
- listener.mListener.onGpsStarted();
- } else {
- listener.mListener.onGpsStopped();
- }
- } catch (RemoteException e) {
- Log.w(TAG, "RemoteException in reportStatus");
- mListeners.remove(listener);
- // adjust for size of list changing
- size--;
- }
- }
-
- // send an intent to notify that the GPS has been enabled or disabled.
- Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION);
- intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- }
+ // send an intent to notify that the GPS has been enabled or disabled
+ Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION);
+ intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating);
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
}
@@ -1282,25 +1219,16 @@
* called from native code to update SV info
*/
private void reportSvStatus() {
-
int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks);
-
- synchronized (mListeners) {
- int size = mListeners.size();
- for (int i = 0; i < size; i++) {
- Listener listener = mListeners.get(i);
- try {
- listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs,
- mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK],
- mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]);
- } catch (RemoteException e) {
- Log.w(TAG, "RemoteException in reportSvInfo");
- mListeners.remove(listener);
- // adjust for size of list changing
- size--;
- }
- }
- }
+ mListenerHelper.onSvStatusChanged(
+ svCount,
+ mSvs,
+ mSnrs,
+ mSvElevations,
+ mSvAzimuths,
+ mSvMasks[EPHEMERIS_MASK],
+ mSvMasks[ALMANAC_MASK],
+ mSvMasks[USED_FOR_FIX_MASK]);
if (VERBOSE) {
Log.v(TAG, "SV count: " + svCount +
@@ -1398,26 +1326,16 @@
* called from native code to report NMEA data received
*/
private void reportNmea(long timestamp) {
- synchronized (mListeners) {
- int size = mListeners.size();
- if (size > 0) {
- // don't bother creating the String if we have no listeners
- int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);
- String nmea = new String(mNmeaBuffer, 0, length);
+ int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);
+ String nmea = new String(mNmeaBuffer, 0 /* offset */, length);
+ mListenerHelper.onNmeaReceived(timestamp, nmea);
+ }
- for (int i = 0; i < size; i++) {
- Listener listener = mListeners.get(i);
- try {
- listener.mListener.onNmeaReceived(timestamp, nmea);
- } catch (RemoteException e) {
- Log.w(TAG, "RemoteException in reportNmea");
- mListeners.remove(listener);
- // adjust for size of list changing
- size--;
- }
- }
- }
- }
+ /**
+ * called from native code - Gps Data callback
+ */
+ private void reportMeasurementData(GpsMeasurementsEvent event) {
+ mGpsMeasurementsProvider.onMeasurementsAvailable(event);
}
/**
@@ -1845,7 +1763,7 @@
Log.e(TAG, "No APN found to select.");
}
} catch (Exception e) {
- Log.e(TAG, "Error encountered on selectiong the APN.", e);
+ Log.e(TAG, "Error encountered on selecting the APN.", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -2008,4 +1926,9 @@
private static native boolean native_remove_geofence(int geofenceId);
private static native boolean native_resume_geofence(int geofenceId, int transitions);
private static native boolean native_pause_geofence(int geofenceId);
+
+ // Gps Hal measurements support.
+ private static native boolean native_is_measurement_supported();
+ private static native boolean native_start_measurement_collection();
+ private static native boolean native_stop_measurement_collection();
}
diff --git a/services/core/java/com/android/server/location/GpsMeasurementsProvider.java b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java
new file mode 100644
index 0000000..001f638
--- /dev/null
+++ b/services/core/java/com/android/server/location/GpsMeasurementsProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import android.location.GpsMeasurementsEvent;
+import android.location.IGpsMeasurementsListener;
+import android.os.RemoteException;
+
+/**
+ * An base implementation for GPS measurements provider.
+ * It abstracts out the responsibility of handling listeners, while still allowing technology
+ * specific implementations to be built.
+ *
+ * @hide
+ */
+public abstract class GpsMeasurementsProvider
+ extends RemoteListenerHelper<IGpsMeasurementsListener> {
+
+ public void onMeasurementsAvailable(final GpsMeasurementsEvent event) {
+ ListenerOperation<IGpsMeasurementsListener> operation =
+ new ListenerOperation<IGpsMeasurementsListener>() {
+ @Override
+ public void execute(IGpsMeasurementsListener listener) throws RemoteException {
+ listener.onGpsMeasurementsReceived(event);
+ }
+ };
+
+ foreach(operation);
+ }
+}
diff --git a/services/core/java/com/android/server/location/GpsStatusListenerHelper.java b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java
new file mode 100644
index 0000000..b741e75
--- /dev/null
+++ b/services/core/java/com/android/server/location/GpsStatusListenerHelper.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import android.location.IGpsStatusListener;
+import android.os.RemoteException;
+
+/**
+ * Implementation of a handler for {@link IGpsStatusListener}.
+ */
+abstract class GpsStatusListenerHelper extends RemoteListenerHelper<IGpsStatusListener> {
+ public void onFirstFix(final int timeToFirstFix) {
+ Operation operation = new Operation() {
+ @Override
+ public void execute(IGpsStatusListener listener) throws RemoteException {
+ listener.onFirstFix(timeToFirstFix);
+ }
+ };
+
+ foreach(operation);
+ }
+
+ public void onStatusChanged(final boolean isNavigating) {
+ Operation operation = new Operation() {
+ @Override
+ public void execute(IGpsStatusListener listener) throws RemoteException {
+ if (isNavigating) {
+ listener.onGpsStarted();
+ } else {
+ listener.onGpsStopped();
+ }
+ }
+ };
+
+ foreach(operation);
+ }
+
+ public void onSvStatusChanged(
+ final int svCount,
+ final int[] prns,
+ final float[] snrs,
+ final float[] elevations,
+ final float[] azimuths,
+ final int ephemerisMask,
+ final int almanacMask,
+ final int usedInFixMask) {
+ Operation operation = new Operation() {
+ @Override
+ public void execute(IGpsStatusListener listener) throws RemoteException {
+ listener.onSvStatusChanged(
+ svCount,
+ prns,
+ snrs,
+ elevations,
+ azimuths,
+ ephemerisMask,
+ almanacMask,
+ usedInFixMask);
+ }
+ };
+
+ foreach(operation);
+ }
+
+ public void onNmeaReceived(final long timestamp, final String nmea) {
+ Operation operation = new Operation() {
+ @Override
+ public void execute(IGpsStatusListener listener) throws RemoteException {
+ listener.onNmeaReceived(timestamp, nmea);
+ }
+ };
+
+ foreach(operation);
+ }
+
+ private abstract class Operation implements ListenerOperation<IGpsStatusListener> { }
+}
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
new file mode 100644
index 0000000..79335f7
--- /dev/null
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.location;
+
+import com.android.internal.util.Preconditions;
+
+import android.annotation.NonNull;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+
+/**
+ * A helper class, that handles operations in remote listeners, and tracks for remote process death.
+ */
+abstract class RemoteListenerHelper<TListener extends IInterface> {
+ private static final String TAG = "RemoteListenerHelper";
+
+ private final HashMap<IBinder, LinkedListener> mListenerMap =
+ new HashMap<IBinder, LinkedListener>();
+
+ public boolean addListener(@NonNull TListener listener) {
+ Preconditions.checkNotNull(listener, "Attempted to register a 'null' listener.");
+
+ if (!isSupported()) {
+ Log.e(TAG, "Refused to add listener, the feature is not supported.");
+ return false;
+ }
+
+ IBinder binder = listener.asBinder();
+ LinkedListener deathListener = new LinkedListener(listener);
+ synchronized (mListenerMap) {
+ if (mListenerMap.containsKey(binder)) {
+ // listener already added
+ return true;
+ }
+
+ try {
+ binder.linkToDeath(deathListener, 0 /* flags */);
+ } catch (RemoteException e) {
+ // if the remote process registering the listener is already death, just swallow the
+ // exception and continue
+ Log.e(TAG, "Remote listener already died.", e);
+ return false;
+ }
+
+ mListenerMap.put(binder, deathListener);
+ if (mListenerMap.size() == 1) {
+ onFirstListenerAdded();
+ }
+ }
+
+ return true;
+ }
+
+ public boolean removeListener(@NonNull TListener listener) {
+ Preconditions.checkNotNull(listener, "Attempted to remove a 'null' listener.");
+
+ if (!isSupported()) {
+ Log.e(TAG, "Refused to remove listener, the feature is not supported.");
+ return false;
+ }
+
+ IBinder binder = listener.asBinder();
+ LinkedListener linkedListener;
+ synchronized (mListenerMap) {
+ linkedListener = mListenerMap.remove(binder);
+ if (mListenerMap.isEmpty() && linkedListener != null) {
+ onLastListenerRemoved();
+ }
+ }
+
+ if (linkedListener != null) {
+ binder.unlinkToDeath(linkedListener, 0 /* flags */);
+ }
+
+ return true;
+ }
+
+ protected abstract boolean isSupported();
+
+ protected void onFirstListenerAdded() {
+ // event triggered when the first listener has been added
+ }
+
+ protected void onLastListenerRemoved() {
+ // event triggered when the last listener has bee removed
+ }
+
+ protected interface ListenerOperation<TListener extends IInterface> {
+ void execute(TListener listener) throws RemoteException;
+ }
+
+ protected void foreach(ListenerOperation operation) {
+ Collection<LinkedListener> linkedListeners;
+ synchronized (mListenerMap) {
+ Collection<LinkedListener> values = mListenerMap.values();
+ linkedListeners = new ArrayList<LinkedListener>(values);
+ }
+
+ for (LinkedListener linkedListener : linkedListeners) {
+ TListener listener = linkedListener.getUnderlyingListener();
+ try {
+ operation.execute(listener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in monitored listener.", e);
+ removeListener(listener);
+ }
+ }
+ }
+
+ private class LinkedListener implements IBinder.DeathRecipient {
+ private final TListener mListener;
+
+ public LinkedListener(@NonNull TListener listener) {
+ mListener = listener;
+ }
+
+ @NonNull
+ public TListener getUnderlyingListener() {
+ return mListener;
+ }
+
+ @Override
+ public void binderDied() {
+ Log.d(TAG, "Remote Listener died: " + mListener);
+ removeListener(mListener);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 871c94f..c19951f 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -52,13 +52,11 @@
/** Sentinel value returned when public key is not found. */
protected static final long PUBLIC_KEY_NOT_FOUND = -1;
- private final Object mLockObject = new Object();
-
private final LongSparseArray<KeySet> mKeySets;
private final LongSparseArray<PublicKey> mPublicKeys;
- protected final LongSparseArray<Set<Long>> mKeySetMapping;
+ protected final LongSparseArray<ArraySet<Long>> mKeySetMapping;
private final Map<String, PackageSetting> mPackages;
@@ -69,7 +67,7 @@
public KeySetManagerService(Map<String, PackageSetting> packages) {
mKeySets = new LongSparseArray<KeySet>();
mPublicKeys = new LongSparseArray<PublicKey>();
- mKeySetMapping = new LongSparseArray<Set<Long>>();
+ mKeySetMapping = new LongSparseArray<ArraySet<Long>>();
mPackages = packages;
}
@@ -84,18 +82,16 @@
*
* Note that this can return true for multiple KeySets.
*/
- public boolean packageIsSignedBy(String packageName, KeySet ks) {
- synchronized (mLockObject) {
- PackageSetting pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new NullPointerException("Invalid package name");
- }
- if (pkg.keySetData == null) {
- throw new NullPointerException("Package has no KeySet data");
- }
- long id = getIdByKeySetLocked(ks);
- return pkg.keySetData.packageIsSignedBy(id);
+ public boolean packageIsSignedByLPr(String packageName, KeySet ks) {
+ PackageSetting pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ throw new NullPointerException("Invalid package name");
}
+ if (pkg.keySetData == null) {
+ throw new NullPointerException("Package has no KeySet data");
+ }
+ long id = getIdByKeySetLPr(ks);
+ return pkg.keySetData.packageIsSignedBy(id);
}
/**
@@ -103,22 +99,20 @@
* in its manifest that a) contains the given keys and b) is named
* alias by that package.
*/
- public void addDefinedKeySetToPackage(String packageName,
+ public void addDefinedKeySetToPackageLPw(String packageName,
Set<PublicKey> keys, String alias) {
if ((packageName == null) || (keys == null) || (alias == null)) {
Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
return;
}
- synchronized (mLockObject) {
- PackageSetting pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new NullPointerException("Unknown package");
- }
- // Add to KeySets, then to package
- KeySet ks = addKeySetLocked(keys);
- long id = getIdByKeySetLocked(ks);
- pkg.keySetData.addDefinedKeySet(id, alias);
+ PackageSetting pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ throw new NullPointerException("Unknown package");
}
+ // Add to KeySets, then to package
+ KeySet ks = addKeySetLPw(keys);
+ long id = getIdByKeySetLPr(ks);
+ pkg.keySetData.addDefinedKeySet(id, alias);
}
/**
@@ -126,53 +120,49 @@
* alias in its manifest to be an upgradeKeySet. This must be called
* after all of the defined KeySets have been added.
*/
- public void addUpgradeKeySetToPackage(String packageName, String alias) {
+ public void addUpgradeKeySetToPackageLPw(String packageName, String alias) {
if ((packageName == null) || (alias == null)) {
Slog.w(TAG, "Got null argument for a defined keyset, ignoring!");
return;
}
- synchronized (mLockObject) {
- PackageSetting pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new NullPointerException("Unknown package");
- }
- pkg.keySetData.addUpgradeKeySet(alias);
+ PackageSetting pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ throw new NullPointerException("Unknown package");
}
+ pkg.keySetData.addUpgradeKeySet(alias);
}
/**
* Similar to the above, this informs the system that the given package
* was signed by the provided KeySet.
*/
- public void addSigningKeySetToPackage(String packageName,
+ public void addSigningKeySetToPackageLPw(String packageName,
Set<PublicKey> signingKeys) {
if ((packageName == null) || (signingKeys == null)) {
Slog.w(TAG, "Got null argument for a signing keyset, ignoring!");
return;
}
- synchronized (mLockObject) {
- // add the signing KeySet
- KeySet ks = addKeySetLocked(signingKeys);
- long id = getIdByKeySetLocked(ks);
- Set<Long> publicKeyIds = mKeySetMapping.get(id);
- if (publicKeyIds == null) {
- throw new NullPointerException("Got invalid KeySet id");
- }
+ // add the signing KeySet
+ KeySet ks = addKeySetLPw(signingKeys);
+ long id = getIdByKeySetLPr(ks);
+ Set<Long> publicKeyIds = mKeySetMapping.get(id);
+ if (publicKeyIds == null) {
+ throw new NullPointerException("Got invalid KeySet id");
+ }
- // attach it to the package
- PackageSetting pkg = mPackages.get(packageName);
- if (pkg == null) {
- throw new NullPointerException("No such package!");
- }
- pkg.keySetData.setProperSigningKeySet(id);
- // for each KeySet which is a subset of the one above, add the
- // KeySet id to the package's signing KeySets
- for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
- long keySetID = mKeySets.keyAt(keySetIndex);
- Set<Long> definedKeys = mKeySetMapping.get(keySetID);
- if (publicKeyIds.containsAll(definedKeys)) {
- pkg.keySetData.addSigningKeySet(keySetID);
- }
+ // attach it to the package
+ PackageSetting pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ throw new NullPointerException("No such package!");
+ }
+ pkg.keySetData.setProperSigningKeySet(id);
+ // for each KeySet which is a subset of the one above, add the
+ // KeySet id to the package's signing KeySets
+ for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
+ long keySetID = mKeySets.keyAt(keySetIndex);
+ Set<Long> definedKeys = mKeySetMapping.get(keySetID);
+ if (publicKeyIds.containsAll(definedKeys)) {
+ pkg.keySetData.addSigningKeySet(keySetID);
}
}
}
@@ -181,13 +171,7 @@
* Fetches the stable identifier associated with the given KeySet. Returns
* {@link #KEYSET_NOT_FOUND} if the KeySet... wasn't found.
*/
- public long getIdByKeySet(KeySet ks) {
- synchronized (mLockObject) {
- return getIdByKeySetLocked(ks);
- }
- }
-
- private long getIdByKeySetLocked(KeySet ks) {
+ private long getIdByKeySetLPr(KeySet ks) {
for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
KeySet value = mKeySets.valueAt(keySetIndex);
if (ks.equals(value)) {
@@ -203,10 +187,8 @@
* Returns {@link #KEYSET_NOT_FOUND} if the identifier doesn't
* identify a {@link KeySet}.
*/
- public KeySet getKeySetById(long id) {
- synchronized (mLockObject) {
- return mKeySets.get(id);
- }
+ public KeySet getKeySetByIdLPr(long id) {
+ return mKeySets.get(id);
}
/**
@@ -215,18 +197,16 @@
* @throws IllegalArgumentException if the package has no keyset data.
* @throws NullPointerException if the package is unknown.
*/
- public KeySet getKeySetByAliasAndPackageName(String packageName, String alias) {
- synchronized (mLockObject) {
- PackageSetting p = mPackages.get(packageName);
- if (p == null) {
- throw new NullPointerException("Unknown package");
- }
- if (p.keySetData == null) {
- throw new IllegalArgumentException("Package has no keySet data");
- }
- long keySetId = p.keySetData.getAliases().get(alias);
- return mKeySets.get(keySetId);
+ public KeySet getKeySetByAliasAndPackageNameLPr(String packageName, String alias) {
+ PackageSetting p = mPackages.get(packageName);
+ if (p == null) {
+ throw new NullPointerException("Unknown package");
}
+ if (p.keySetData == null) {
+ throw new IllegalArgumentException("Package has no keySet data");
+ }
+ long keySetId = p.keySetData.getAliases().get(alias);
+ return mKeySets.get(keySetId);
}
/**
@@ -236,17 +216,15 @@
* Returns {@code null} if the identifier doesn't
* identify a {@link KeySet}.
*/
- public ArraySet<PublicKey> getPublicKeysFromKeySet(long id) {
- synchronized (mLockObject) {
- if(mKeySetMapping.get(id) == null) {
- return null;
- }
- ArraySet<PublicKey> mPubKeys = new ArraySet<PublicKey>();
- for (long pkId : mKeySetMapping.get(id)) {
- mPubKeys.add(mPublicKeys.get(pkId));
- }
- return mPubKeys;
+ public ArraySet<PublicKey> getPublicKeysFromKeySetLPr(long id) {
+ if(mKeySetMapping.get(id) == null) {
+ return null;
}
+ ArraySet<PublicKey> mPubKeys = new ArraySet<PublicKey>();
+ for (long pkId : mKeySetMapping.get(id)) {
+ mPubKeys.add(mPublicKeys.get(pkId));
+ }
+ return mPubKeys;
}
/**
@@ -256,21 +234,19 @@
* @throws IllegalArgumentException if the package has no keyset data.
* @throws NullPointerException if the package is unknown.
*/
- public Set<KeySet> getSigningKeySetsByPackageName(String packageName) {
- synchronized (mLockObject) {
- Set<KeySet> signingKeySets = new ArraySet<KeySet>();
- PackageSetting p = mPackages.get(packageName);
- if (p == null) {
- throw new NullPointerException("Unknown package");
- }
- if (p.keySetData == null || p.keySetData.getSigningKeySets() == null) {
- throw new IllegalArgumentException("Package has no keySet data");
- }
- for (long l : p.keySetData.getSigningKeySets()) {
- signingKeySets.add(mKeySets.get(l));
- }
- return signingKeySets;
+ public Set<KeySet> getSigningKeySetsByPackageNameLPr(String packageName) {
+ Set<KeySet> signingKeySets = new ArraySet<KeySet>();
+ PackageSetting p = mPackages.get(packageName);
+ if (p == null) {
+ throw new NullPointerException("Unknown package");
}
+ if (p.keySetData == null || p.keySetData.getSigningKeySets() == null) {
+ throw new IllegalArgumentException("Package has no keySet data");
+ }
+ for (long l : p.keySetData.getSigningKeySets()) {
+ signingKeySets.add(mKeySets.get(l));
+ }
+ return signingKeySets;
}
/**
@@ -280,23 +256,21 @@
* @throws IllegalArgumentException if the package has no keyset data.
* @throws NullPointerException if the package is unknown.
*/
- public ArraySet<KeySet> getUpgradeKeySetsByPackageName(String packageName) {
- synchronized (mLockObject) {
- ArraySet<KeySet> upgradeKeySets = new ArraySet<KeySet>();
- PackageSetting p = mPackages.get(packageName);
- if (p == null) {
- throw new NullPointerException("Unknown package");
- }
- if (p.keySetData == null) {
- throw new IllegalArgumentException("Package has no keySet data");
- }
- if (p.keySetData.isUsingUpgradeKeySets()) {
- for (long l : p.keySetData.getUpgradeKeySets()) {
- upgradeKeySets.add(mKeySets.get(l));
- }
- }
- return upgradeKeySets;
+ public ArraySet<KeySet> getUpgradeKeySetsByPackageNameLPr(String packageName) {
+ ArraySet<KeySet> upgradeKeySets = new ArraySet<KeySet>();
+ PackageSetting p = mPackages.get(packageName);
+ if (p == null) {
+ throw new NullPointerException("Unknown package");
}
+ if (p.keySetData == null) {
+ throw new IllegalArgumentException("Package has no keySet data");
+ }
+ if (p.keySetData.isUsingUpgradeKeySets()) {
+ for (long l : p.keySetData.getUpgradeKeySets()) {
+ upgradeKeySets.add(mKeySets.get(l));
+ }
+ }
+ return upgradeKeySets;
}
/**
@@ -313,19 +287,19 @@
*
* Throws if the provided set is {@code null}.
*/
- private KeySet addKeySetLocked(Set<PublicKey> keys) {
+ private KeySet addKeySetLPw(Set<PublicKey> keys) {
if (keys == null) {
throw new NullPointerException("Provided keys cannot be null");
}
// add each of the keys in the provided set
- Set<Long> addedKeyIds = new ArraySet<Long>(keys.size());
+ ArraySet<Long> addedKeyIds = new ArraySet<Long>(keys.size());
for (PublicKey k : keys) {
- long id = addPublicKeyLocked(k);
+ long id = addPublicKeyLPw(k);
addedKeyIds.add(id);
}
// check to see if the resulting keyset is new
- long existingKeySetId = getIdFromKeyIdsLocked(addedKeyIds);
+ long existingKeySetId = getIdFromKeyIdsLPr(addedKeyIds);
if (existingKeySetId != KEYSET_NOT_FOUND) {
return mKeySets.get(existingKeySetId);
}
@@ -333,7 +307,7 @@
// create the KeySet object
KeySet ks = new KeySet(new Binder());
// get the first unoccupied slot in mKeySets
- long id = getFreeKeySetIDLocked();
+ long id = getFreeKeySetIDLPw();
// add the KeySet object to it
mKeySets.put(id, ks);
// add the stable key ids to the mapping
@@ -358,14 +332,14 @@
/**
* Adds the given PublicKey to the system, deduping as it goes.
*/
- private long addPublicKeyLocked(PublicKey key) {
+ private long addPublicKeyLPw(PublicKey key) {
// check if the public key is new
- long existingKeyId = getIdForPublicKeyLocked(key);
+ long existingKeyId = getIdForPublicKeyLPr(key);
if (existingKeyId != PUBLIC_KEY_NOT_FOUND) {
return existingKeyId;
}
// if it's new find the first unoccupied slot in the public keys
- long id = getFreePublicKeyIdLocked();
+ long id = getFreePublicKeyIdLPw();
// add the public key to it
mPublicKeys.put(id, key);
// return the stable identifier
@@ -377,7 +351,7 @@
*
* Returns KEYSET_NOT_FOUND if there isn't one.
*/
- private long getIdFromKeyIdsLocked(Set<Long> publicKeyIds) {
+ private long getIdFromKeyIdsLPr(Set<Long> publicKeyIds) {
for (int keyMapIndex = 0; keyMapIndex < mKeySetMapping.size(); keyMapIndex++) {
Set<Long> value = mKeySetMapping.valueAt(keyMapIndex);
if (value.equals(publicKeyIds)) {
@@ -390,16 +364,7 @@
/**
* Finds the stable identifier for a PublicKey or PUBLIC_KEY_NOT_FOUND.
*/
- protected long getIdForPublicKey(PublicKey k) {
- synchronized (mLockObject) {
- return getIdForPublicKeyLocked(k);
- }
- }
-
- /**
- * Finds the stable identifier for a PublicKey or PUBLIC_KEY_NOT_FOUND.
- */
- private long getIdForPublicKeyLocked(PublicKey k) {
+ private long getIdForPublicKeyLPr(PublicKey k) {
String encodedPublicKey = new String(k.getEncoded());
for (int publicKeyIndex = 0; publicKeyIndex < mPublicKeys.size(); publicKeyIndex++) {
PublicKey value = mPublicKeys.valueAt(publicKeyIndex);
@@ -414,7 +379,7 @@
/**
* Gets an unused stable identifier for a KeySet.
*/
- private long getFreeKeySetIDLocked() {
+ private long getFreeKeySetIDLPw() {
lastIssuedKeySetId += 1;
return lastIssuedKeySetId;
}
@@ -422,72 +387,69 @@
/**
* Same as above, but for public keys.
*/
- private long getFreePublicKeyIdLocked() {
+ private long getFreePublicKeyIdLPw() {
lastIssuedKeyId += 1;
return lastIssuedKeyId;
}
- public void removeAppKeySetData(String packageName) {
- synchronized (mLockObject) {
- // Get the package's known keys and KeySets
- Set<Long> deletableKeySets = getOriginalKeySetsByPackageNameLocked(packageName);
- Set<Long> deletableKeys = new ArraySet<Long>();
- Set<Long> knownKeys = null;
- for (Long ks : deletableKeySets) {
+ public void removeAppKeySetDataLPw(String packageName) {
+ // Get the package's known keys and KeySets
+ ArraySet<Long> deletableKeySets = getOriginalKeySetsByPackageNameLPr(packageName);
+ ArraySet<Long> deletableKeys = new ArraySet<Long>();
+ ArraySet<Long> knownKeys = null;
+ for (Long ks : deletableKeySets) {
+ knownKeys = mKeySetMapping.get(ks);
+ if (knownKeys != null) {
+ deletableKeys.addAll(knownKeys);
+ }
+ }
+
+ // Now remove the keys and KeySets on which any other package relies
+ for (String pkgName : mPackages.keySet()) {
+ if (pkgName.equals(packageName)) {
+ continue;
+ }
+ ArraySet<Long> knownKeySets = getOriginalKeySetsByPackageNameLPr(pkgName);
+ deletableKeySets.removeAll(knownKeySets);
+ knownKeys = new ArraySet<Long>();
+ for (Long ks : knownKeySets) {
knownKeys = mKeySetMapping.get(ks);
if (knownKeys != null) {
- deletableKeys.addAll(knownKeys);
+ deletableKeys.removeAll(knownKeys);
}
}
-
- // Now remove the keys and KeySets on which any other package relies
- for (String pkgName : mPackages.keySet()) {
- if (pkgName.equals(packageName)) {
- continue;
- }
- Set<Long> knownKeySets = getOriginalKeySetsByPackageNameLocked(pkgName);
- deletableKeySets.removeAll(knownKeySets);
- knownKeys = new ArraySet<Long>();
- for (Long ks : knownKeySets) {
- knownKeys = mKeySetMapping.get(ks);
- if (knownKeys != null) {
- deletableKeys.removeAll(knownKeys);
- }
- }
- }
-
- // The remaining keys and KeySets are not relied on by any other
- // application and so can be safely deleted.
- for (Long ks : deletableKeySets) {
- mKeySets.delete(ks);
- mKeySetMapping.delete(ks);
- }
- for (Long keyId : deletableKeys) {
- mPublicKeys.delete(keyId);
- }
-
- // Now remove the deleted KeySets from each package's signingKeySets
- for (String pkgName : mPackages.keySet()) {
- PackageSetting p = mPackages.get(pkgName);
- for (Long ks : deletableKeySets) {
- p.keySetData.removeSigningKeySet(ks);
- }
- }
-
- // Finally, remove all KeySets from the original package
- PackageSetting p = mPackages.get(packageName);
- clearPackageKeySetDataLocked(p);
}
+
+ // The remaining keys and KeySets are not relied on by any other
+ // application and so can be safely deleted.
+ for (Long ks : deletableKeySets) {
+ mKeySets.delete(ks);
+ mKeySetMapping.delete(ks);
+ }
+ for (Long keyId : deletableKeys) {
+ mPublicKeys.delete(keyId);
+ }
+
+ // Now remove the deleted KeySets from each package's signingKeySets
+ for (String pkgName : mPackages.keySet()) {
+ PackageSetting p = mPackages.get(pkgName);
+ for (Long ks : deletableKeySets) {
+ p.keySetData.removeSigningKeySet(ks);
+ }
+ }
+ // Finally, remove all KeySets from the original package
+ PackageSetting p = mPackages.get(packageName);
+ clearPackageKeySetDataLPw(p);
}
- private void clearPackageKeySetDataLocked(PackageSetting p) {
+ private void clearPackageKeySetDataLPw(PackageSetting p) {
p.keySetData.removeAllSigningKeySets();
p.keySetData.removeAllUpgradeKeySets();
p.keySetData.removeAllDefinedKeySets();
return;
}
- private Set<Long> getOriginalKeySetsByPackageNameLocked(String packageName) {
+ private ArraySet<Long> getOriginalKeySetsByPackageNameLPr(String packageName) {
PackageSetting p = mPackages.get(packageName);
if (p == null) {
throw new NullPointerException("Unknown package");
@@ -495,7 +457,7 @@
if (p.keySetData == null) {
throw new IllegalArgumentException("Package has no keySet data");
}
- Set<Long> knownKeySets = new ArraySet<Long>();
+ ArraySet<Long> knownKeySets = new ArraySet<Long>();
knownKeySets.add(p.keySetData.getProperSigningKeySet());
if (p.keySetData.isUsingDefinedKeySets()) {
for (long ks : p.keySetData.getDefinedKeySets()) {
@@ -509,82 +471,80 @@
return new String(Base64.encode(k.getEncoded(), 0));
}
- public void dump(PrintWriter pw, String packageName,
- PackageManagerService.DumpState dumpState) {
- synchronized (mLockObject) {
- boolean printedHeader = false;
- for (Map.Entry<String, PackageSetting> e : mPackages.entrySet()) {
- String keySetPackage = e.getKey();
- if (packageName != null && !packageName.equals(keySetPackage)) {
- continue;
+ public void dumpLPr(PrintWriter pw, String packageName,
+ PackageManagerService.DumpState dumpState) {
+ boolean printedHeader = false;
+ for (Map.Entry<String, PackageSetting> e : mPackages.entrySet()) {
+ String keySetPackage = e.getKey();
+ if (packageName != null && !packageName.equals(keySetPackage)) {
+ continue;
+ }
+ if (!printedHeader) {
+ if (dumpState.onTitlePrinted())
+ pw.println();
+ pw.println("Key Set Manager:");
+ printedHeader = true;
+ }
+ PackageSetting pkg = e.getValue();
+ pw.print(" ["); pw.print(keySetPackage); pw.println("]");
+ if (pkg.keySetData != null) {
+ boolean printedLabel = false;
+ for (Map.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) {
+ if (!printedLabel) {
+ pw.print(" KeySets Aliases: ");
+ printedLabel = true;
+ } else {
+ pw.print(", ");
+ }
+ pw.print(entry.getKey());
+ pw.print('=');
+ pw.print(Long.toString(entry.getValue()));
}
- if (!printedHeader) {
- if (dumpState.onTitlePrinted())
- pw.println();
- pw.println("Key Set Manager:");
- printedHeader = true;
+ if (printedLabel) {
+ pw.println("");
}
- PackageSetting pkg = e.getValue();
- pw.print(" ["); pw.print(keySetPackage); pw.println("]");
- if (pkg.keySetData != null) {
- boolean printedLabel = false;
- for (Map.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) {
+ printedLabel = false;
+ if (pkg.keySetData.isUsingDefinedKeySets()) {
+ for (long keySetId : pkg.keySetData.getDefinedKeySets()) {
if (!printedLabel) {
- pw.print(" KeySets Aliases: ");
- printedLabel = true;
- } else {
- pw.print(", ");
- }
- pw.print(entry.getKey());
- pw.print('=');
- pw.print(Long.toString(entry.getValue()));
- }
- if (printedLabel) {
- pw.println("");
- }
- printedLabel = false;
- if (pkg.keySetData.isUsingDefinedKeySets()) {
- for (long keySetId : pkg.keySetData.getDefinedKeySets()) {
- if (!printedLabel) {
- pw.print(" Defined KeySets: ");
- printedLabel = true;
- } else {
- pw.print(", ");
- }
- pw.print(Long.toString(keySetId));
- }
- }
- if (printedLabel) {
- pw.println("");
- }
- printedLabel = false;
- for (long keySetId : pkg.keySetData.getSigningKeySets()) {
- if (!printedLabel) {
- pw.print(" Signing KeySets: ");
+ pw.print(" Defined KeySets: ");
printedLabel = true;
} else {
pw.print(", ");
}
pw.print(Long.toString(keySetId));
}
- if (printedLabel) {
- pw.println("");
+ }
+ if (printedLabel) {
+ pw.println("");
+ }
+ printedLabel = false;
+ for (long keySetId : pkg.keySetData.getSigningKeySets()) {
+ if (!printedLabel) {
+ pw.print(" Signing KeySets: ");
+ printedLabel = true;
+ } else {
+ pw.print(", ");
}
- printedLabel = false;
- if (pkg.keySetData.isUsingUpgradeKeySets()) {
- for (long keySetId : pkg.keySetData.getUpgradeKeySets()) {
- if (!printedLabel) {
- pw.print(" Upgrade KeySets: ");
- printedLabel = true;
- } else {
- pw.print(", ");
- }
- pw.print(Long.toString(keySetId));
+ pw.print(Long.toString(keySetId));
+ }
+ if (printedLabel) {
+ pw.println("");
+ }
+ printedLabel = false;
+ if (pkg.keySetData.isUsingUpgradeKeySets()) {
+ for (long keySetId : pkg.keySetData.getUpgradeKeySets()) {
+ if (!printedLabel) {
+ pw.print(" Upgrade KeySets: ");
+ printedLabel = true;
+ } else {
+ pw.print(", ");
}
+ pw.print(Long.toString(keySetId));
}
- if (printedLabel) {
- pw.println("");
- }
+ }
+ if (printedLabel) {
+ pw.println("");
}
}
}
@@ -651,7 +611,7 @@
// The KeySet information read previously from packages.xml is invalid.
// Destroy it all.
for (PackageSetting p : mPackages.values()) {
- clearPackageKeySetDataLocked(p);
+ clearPackageKeySetDataLPw(p);
}
return;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 682a38d6..0eb922d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -24,29 +24,36 @@
import android.content.Context;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageInstallerObserver;
import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
import android.os.Binder;
import android.os.FileUtils;
import android.os.HandlerThread;
import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
import android.os.SELinux;
import android.os.UserHandle;
import android.os.UserManager;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
+import android.util.ExceptionUtils;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.IoThread;
import com.google.android.collect.Sets;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
public class PackageInstallerService extends IPackageInstaller.Stub {
private static final String TAG = "PackageInstaller";
@@ -68,6 +75,8 @@
@GuardedBy("mSessions")
private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
+ private RemoteCallbackList<IPackageInstallerObserver> mObservers = new RemoteCallbackList<>();
+
private static final FilenameFilter sStageFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
@@ -138,10 +147,10 @@
}
@Override
- public int createSession(String installerPackageName, PackageInstallerParams params,
+ public int createSession(String installerPackageName, InstallSessionParams params,
int userId) {
final int callingUid = Binder.getCallingUid();
- mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
+ mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession");
if (mPm.isUserRestricted(UserHandle.getUserId(callingUid),
UserManager.DISALLOW_INSTALL_APPS)) {
@@ -158,19 +167,32 @@
params.installFlags |= INSTALL_REPLACE_EXISTING;
}
+ // Sanity check that install could fit
+ if (params.deltaSize > 0) {
+ try {
+ mPm.freeStorage(params.deltaSize);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
+ }
+
+ final int sessionId;
+ final PackageInstallerSession session;
synchronized (mSessions) {
+ sessionId = allocateSessionIdLocked();
+
final long createdMillis = System.currentTimeMillis();
- final int sessionId = allocateSessionIdLocked();
final File sessionStageDir = prepareSessionStageDir(sessionId);
- final PackageInstallerSession session = new PackageInstallerSession(mCallback, mPm,
- sessionId, userId, installerPackageName, callingUid, params, createdMillis,
- sessionStageDir, mInstallThread.getLooper());
+ session = new PackageInstallerSession(mCallback, mPm, sessionId, userId,
+ installerPackageName, callingUid, params, createdMillis, sessionStageDir,
+ mInstallThread.getLooper());
mSessions.put(sessionId, session);
-
- writeSessionsAsync();
- return sessionId;
}
+
+ notifySessionCreated(session.generateInfo());
+ writeSessionsAsync();
+ return sessionId;
}
@Override
@@ -217,44 +239,130 @@
}
@Override
- public int[] getSessions(String installerPackageName, int userId) {
- final int callingUid = Binder.getCallingUid();
- mPm.enforceCrossUserPermission(callingUid, userId, false, TAG);
- mAppOps.checkPackage(callingUid, installerPackageName);
+ public List<InstallSessionInfo> getSessions(int userId) {
+ mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getSessions");
- int[] matching = new int[0];
+ final List<InstallSessionInfo> result = new ArrayList<>();
synchronized (mSessions) {
for (int i = 0; i < mSessions.size(); i++) {
- final int key = mSessions.keyAt(i);
final PackageInstallerSession session = mSessions.valueAt(i);
- if (session.userId == userId
- && session.installerPackageName.equals(installerPackageName)) {
- matching = ArrayUtils.appendInt(matching, key);
+ if (session.userId == userId) {
+ result.add(session.generateInfo());
}
}
}
- return matching;
+ return result;
}
@Override
- public void uninstall(String basePackageName, int flags, IPackageDeleteObserver observer,
+ public void uninstall(String packageName, int flags, IPackageDeleteObserver observer,
int userId) {
- mPm.deletePackageAsUser(basePackageName, observer, userId, flags);
+ mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall");
+ mPm.deletePackageAsUser(packageName, observer, userId, flags);
}
@Override
public void uninstallSplit(String basePackageName, String overlayName, int flags,
IPackageDeleteObserver observer, int userId) {
+ mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstallSplit");
+
// TODO: flesh out once PM has split support
throw new UnsupportedOperationException();
}
+ @Override
+ public void registerObserver(IPackageInstallerObserver observer, int userId) {
+ mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerObserver");
+
+ // TODO: consider restricting to active launcher app only
+ mObservers.register(observer, new UserHandle(userId));
+ }
+
+ @Override
+ public void unregisterObserver(IPackageInstallerObserver observer, int userId) {
+ mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "unregisterObserver");
+ mObservers.unregister(observer);
+ }
+
+ private int getSessionUserId(int sessionId) {
+ synchronized (mSessions) {
+ return UserHandle.getUserId(mSessions.get(sessionId).installerUid);
+ }
+ }
+
+ private void notifySessionCreated(InstallSessionInfo info) {
+ final int userId = getSessionUserId(info.sessionId);
+ final int n = mObservers.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
+ final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
+ if (userId == user.getIdentifier()) {
+ try {
+ observer.onSessionCreated(info);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+ mObservers.finishBroadcast();
+ }
+
+ private void notifySessionProgress(int sessionId, int progress) {
+ final int userId = getSessionUserId(sessionId);
+ final int n = mObservers.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
+ final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
+ if (userId == user.getIdentifier()) {
+ try {
+ observer.onSessionProgress(sessionId, progress);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+ mObservers.finishBroadcast();
+ }
+
+ private void notifySessionFinished(int sessionId, boolean success) {
+ final int userId = getSessionUserId(sessionId);
+ final int n = mObservers.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ final IPackageInstallerObserver observer = mObservers.getBroadcastItem(i);
+ final UserHandle user = (UserHandle) mObservers.getBroadcastCookie(i);
+ if (userId == user.getIdentifier()) {
+ try {
+ observer.onSessionFinished(sessionId, success);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+ mObservers.finishBroadcast();
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Active install sessions:");
+ pw.increaseIndent();
+ synchronized (mSessions) {
+ final int N = mSessions.size();
+ for (int i = 0; i < N; i++) {
+ final PackageInstallerSession session = mSessions.valueAt(i);
+ session.dump(pw);
+ pw.println();
+ }
+ }
+ pw.println();
+ pw.decreaseIndent();
+ }
+
class Callback {
- public void onProgressChanged(PackageInstallerSession session) {
- // TODO: notify listeners
+ public void onSessionProgress(PackageInstallerSession session, int progress) {
+ notifySessionProgress(session.sessionId, progress);
}
- public void onSessionInvalid(PackageInstallerSession session) {
+ public void onSessionFinished(PackageInstallerSession session, boolean success) {
+ notifySessionFinished(session.sessionId, success);
+ synchronized (mSessions) {
+ mSessions.remove(session.sessionId);
+ }
writeSessionsAsync();
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 3448803..c399fa2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -26,7 +26,8 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstallerSession;
-import android.content.pm.PackageInstallerParams;
+import android.content.pm.InstallSessionInfo;
+import android.content.pm.InstallSessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ApkLite;
@@ -47,9 +48,12 @@
import android.system.OsConstants;
import android.system.StructStat;
import android.util.ArraySet;
+import android.util.ExceptionUtils;
+import android.util.MathUtils;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import libcore.io.Libcore;
@@ -75,7 +79,7 @@
public final String installerPackageName;
/** UID not persisted */
public final int installerUid;
- public final PackageInstallerParams params;
+ public final InstallSessionParams params;
public final long createdMillis;
public final File sessionStageDir;
@@ -91,13 +95,15 @@
try {
installLocked();
- } catch (InstallFailedException e) {
+ } catch (PackageManagerException e) {
Slog.e(TAG, "Install failed: " + e);
- destroy();
+ destroyInternal();
try {
- mRemoteObserver.packageInstalled(mPackageName, null, e.error);
+ mRemoteObserver.packageInstalled(mPackageName, null, e.error,
+ e.getMessage());
} catch (RemoteException ignored) {
}
+ mCallback.onSessionFinished(PackageInstallerSession.this, false);
}
return true;
@@ -107,7 +113,8 @@
private final Object mLock = new Object();
- private int mProgress;
+ private int mClientProgress;
+ private int mProgress = 0;
private String mPackageName;
private int mVersionCode;
@@ -123,7 +130,7 @@
public PackageInstallerSession(PackageInstallerService.Callback callback,
PackageManagerService pm, int sessionId, int userId, String installerPackageName,
- int installerUid, PackageInstallerParams params, long createdMillis, File sessionStageDir,
+ int installerUid, InstallSessionParams params, long createdMillis, File sessionStageDir,
Looper looper) {
mCallback = callback;
mPm = pm;
@@ -152,14 +159,44 @@
}
}
+ public InstallSessionInfo generateInfo() {
+ final InstallSessionInfo info = new InstallSessionInfo();
+
+ info.sessionId = sessionId;
+ info.installerPackageName = installerPackageName;
+ info.progress = mProgress;
+
+ info.fullInstall = params.fullInstall;
+ info.packageName = params.packageName;
+ info.icon = params.icon;
+ info.title = params.title;
+
+ return info;
+ }
+
@Override
- public void updateProgress(int progress) {
- mProgress = progress;
- mCallback.onProgressChanged(this);
+ public void setClientProgress(int progress) {
+ mClientProgress = progress;
+ mProgress = MathUtils.constrain((mClientProgress * 8 * 100) / (params.progressMax * 10), 0, 80);
+ mCallback.onSessionProgress(this, mProgress);
+ }
+
+ @Override
+ public void addClientProgress(int progress) {
+ setClientProgress(mClientProgress + progress);
}
@Override
public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
+ try {
+ return openWriteInternal(name, offsetBytes, lengthBytes);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
+ }
+
+ private ParcelFileDescriptor openWriteInternal(String name, long offsetBytes, long lengthBytes)
+ throws IOException {
// TODO: relay over to DCS when installing to ASEC
// Quick sanity check of state, and allocate a pipe for ourselves. We
@@ -206,9 +243,7 @@
return new ParcelFileDescriptor(bridge.getClientSocket());
} catch (ErrnoException e) {
- throw new IllegalStateException("Failed to write", e);
- } catch (IOException e) {
- throw new IllegalStateException("Failed to write", e);
+ throw e.rethrowAsIOException();
}
}
@@ -218,16 +253,16 @@
mHandler.obtainMessage(MSG_INSTALL, observer).sendToTarget();
}
- private void installLocked() throws InstallFailedException {
+ private void installLocked() throws PackageManagerException {
if (mInvalid) {
- throw new InstallFailedException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
+ throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
}
// Verify that all writers are hands-off
if (mMutationsAllowed) {
for (FileBridge bridge : mBridges) {
if (!bridge.isClosed()) {
- throw new InstallFailedException(INSTALL_FAILED_PACKAGE_CHANGED,
+ throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
"Files still open");
}
}
@@ -257,6 +292,9 @@
spliceExistingFilesIntoStage();
}
+ // TODO: surface more granular state from dexopt
+ mCallback.onSessionProgress(this, 90);
+
// TODO: for ASEC based applications, grow and stream in packages
// We've reached point of no return; call into PMS to install the stage.
@@ -264,15 +302,20 @@
final IPackageInstallObserver2 remoteObserver = mRemoteObserver;
final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
@Override
- public void packageInstalled(String basePackageName, Bundle extras, int returnCode)
- throws RemoteException {
- destroy();
- remoteObserver.packageInstalled(basePackageName, extras, returnCode);
+ public void packageInstalled(String basePackageName, Bundle extras, int returnCode,
+ String msg) {
+ destroyInternal();
+ try {
+ remoteObserver.packageInstalled(basePackageName, extras, returnCode, msg);
+ } catch (RemoteException ignored) {
+ }
+ final boolean success = (returnCode == PackageManager.INSTALL_SUCCEEDED);
+ mCallback.onSessionFinished(PackageInstallerSession.this, success);
}
};
- mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params, installerPackageName,
- installerUid, new UserHandle(userId));
+ mPm.installStage(mPackageName, this.sessionStageDir, localObserver, params,
+ installerPackageName, installerUid, new UserHandle(userId));
}
/**
@@ -281,13 +324,13 @@
* <p>
* Renames package files in stage to match split names defined inside.
*/
- private void validateInstallLocked() throws InstallFailedException {
+ private void validateInstallLocked() throws PackageManagerException {
mPackageName = null;
mVersionCode = -1;
final File[] files = sessionStageDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
- throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, "No packages staged");
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
}
final ArraySet<String> seenSplits = new ArraySet<>();
@@ -298,12 +341,12 @@
try {
info = PackageParser.parseApkLite(file, PackageParser.PARSE_GET_SIGNATURES);
} catch (PackageParserException e) {
- throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Failed to parse " + file + ": " + e);
}
if (!seenSplits.add(info.splitName)) {
- throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Split " + info.splitName + " was defined multiple times");
}
@@ -327,7 +370,7 @@
name = "split_" + info.splitName + ".apk";
}
if (!FileUtils.isValidExtFilename(name)) {
- throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Invalid filename: " + name);
}
if (!file.getName().equals(name)) {
@@ -342,7 +385,7 @@
if (params.fullInstall) {
// Full installs must include a base package
if (!seenSplits.contains(null)) {
- throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Full install must include a base package");
}
@@ -350,7 +393,7 @@
// Partial installs must be consistent with existing install.
final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
if (app == null) {
- throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Missing existing base package for " + mPackageName);
}
@@ -359,7 +402,7 @@
info = PackageParser.parseApkLite(new File(app.getBaseCodePath()),
PackageParser.PARSE_GET_SIGNATURES);
} catch (PackageParserException e) {
- throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Failed to parse existing base " + app.getBaseCodePath() + ": " + e);
}
@@ -369,18 +412,18 @@
}
private void assertPackageConsistent(String tag, String packageName, int versionCode,
- Signature[] signatures) throws InstallFailedException {
+ Signature[] signatures) throws PackageManagerException {
if (!mPackageName.equals(packageName)) {
- throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag + " package "
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
+ packageName + " inconsistent with " + mPackageName);
}
if (mVersionCode != versionCode) {
- throw new InstallFailedException(INSTALL_FAILED_INVALID_APK, tag
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
+ " version code " + versionCode + " inconsistent with "
+ mVersionCode);
}
if (!Signature.areExactMatch(mSignatures, signatures)) {
- throw new InstallFailedException(INSTALL_FAILED_INVALID_APK,
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
tag + " signatures are inconsistent");
}
}
@@ -389,14 +432,14 @@
* Application is already installed; splice existing files that haven't been
* overridden into our stage.
*/
- private void spliceExistingFilesIntoStage() throws InstallFailedException {
+ private void spliceExistingFilesIntoStage() throws PackageManagerException {
final ApplicationInfo app = mPm.getApplicationInfo(mPackageName, 0, userId);
final File existingDir = new File(app.getBaseCodePath());
try {
linkTreeIgnoringExisting(existingDir, sessionStageDir);
} catch (ErrnoException e) {
- throw new InstallFailedException(INSTALL_FAILED_INTERNAL_ERROR,
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
"Failed to splice into stage");
}
}
@@ -425,22 +468,40 @@
@Override
public void destroy() {
try {
- synchronized (mLock) {
- mInvalid = true;
- }
- FileUtils.deleteContents(sessionStageDir);
- sessionStageDir.delete();
+ destroyInternal();
} finally {
- mCallback.onSessionInvalid(this);
+ mCallback.onSessionFinished(this, false);
}
}
- private class InstallFailedException extends Exception {
- private final int error;
-
- public InstallFailedException(int error, String detailMessage) {
- super(detailMessage);
- this.error = error;
+ private void destroyInternal() {
+ synchronized (mLock) {
+ mInvalid = true;
}
+ FileUtils.deleteContents(sessionStageDir);
+ sessionStageDir.delete();
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Session " + sessionId + ":");
+ pw.increaseIndent();
+
+ pw.printPair("userId", userId);
+ pw.printPair("installerPackageName", installerPackageName);
+ pw.printPair("installerUid", installerUid);
+ pw.printPair("createdMillis", createdMillis);
+ pw.printPair("sessionStageDir", sessionStageDir);
+ pw.println();
+
+ params.dump(pw);
+
+ pw.printPair("mClientProgress", mClientProgress);
+ pw.printPair("mProgress", mProgress);
+ pw.printPair("mMutationsAllowed", mMutationsAllowed);
+ pw.printPair("mPermissionsConfirmed", mPermissionsConfirmed);
+ pw.printPair("mBridges", mBridges.size());
+ pw.println();
+
+ pw.decreaseIndent();
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerException.java b/services/core/java/com/android/server/pm/PackageManagerException.java
new file mode 100644
index 0000000..0cbdcdc
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageManagerException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+/** {@hide} */
+public class PackageManagerException extends Exception {
+ public final int error;
+
+ public PackageManagerException(int error, String detailMessage) {
+ super(detailMessage);
+ this.error = error;
+ }
+
+ public PackageManagerException(int error, String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ this.error = error;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d8f4f78..101ef92 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -24,14 +24,30 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static android.content.pm.PackageManager.INSTALL_EXTERNAL;
+import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+import static android.content.pm.PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER;
+import static android.content.pm.PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_DEXOPT;
+import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+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_SHARED_USER_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
+import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED;
+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_FORWARD_LOCK;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
import static android.content.pm.PackageParser.isApkFile;
import static android.os.Process.PACKAGE_INFO_GID;
import static android.os.Process.SYSTEM_UID;
import static android.system.OsConstants.O_CREAT;
-import static android.system.OsConstants.EEXIST;
-import static android.system.OsConstants.O_EXCL;
import static android.system.OsConstants.O_RDWR;
import static android.system.OsConstants.S_IRGRP;
import static android.system.OsConstants.S_IROTH;
@@ -54,6 +70,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
import com.android.server.IntentResolver;
@@ -85,18 +102,17 @@
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstaller;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstallSessionParams;
import android.content.pm.InstrumentationInfo;
import android.content.pm.ManifestDigest;
import android.content.pm.PackageCleanItem;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
-import android.content.pm.PackageInstallerParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser.ActivityIntentInfo;
import android.content.pm.PackageParser.PackageLite;
@@ -185,7 +201,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
@@ -381,9 +396,6 @@
final HashMap<String, PackageParser.Package> mAppDirs =
new HashMap<String, PackageParser.Package>();
- // Information for the parser to write more useful error messages.
- int mLastScanError;
-
// ----------------------------------------------------------------
// Keys are String (package name), values are Package. This also serves
@@ -1080,7 +1092,8 @@
if (args.observer != null) {
try {
Bundle extras = extrasForInstallResult(res);
- args.observer.packageInstalled(res.name, extras, res.returnCode);
+ args.observer.packageInstalled(res.name, extras, res.returnCode,
+ res.returnMsg);
} catch (RemoteException e) {
Slog.i(TAG, "Observer no longer exists.");
}
@@ -2815,7 +2828,9 @@
// Migrate the old signatures to the new scheme.
existingSigs.assignSignatures(scannedPkg.mSignatures);
// The new KeySets will be re-added later in the scanning process.
- mSettings.mKeySetManagerService.removeAppKeySetData(scannedPkg.packageName);
+ synchronized (mPackages) {
+ mSettings.mKeySetManagerService.removeAppKeySetDataLPw(scannedPkg.packageName);
+ }
return PackageManager.SIGNATURE_MATCH;
}
return PackageManager.SIGNATURE_NO_MATCH;
@@ -4094,14 +4109,19 @@
// Ignore entries which are not apk's
continue;
}
- PackageParser.Package pkg = scanPackageLI(file,
- flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null, null);
- // Don't mess around with apps in system partition.
- if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
- mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {
- // Delete the apk
- Slog.w(TAG, "Cleaning up failed install of " + file);
- file.delete();
+ try {
+ scanPackageLI(file, flags | PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime,
+ null, null);
+ } catch (PackageManagerException e) {
+ Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
+
+ // Don't mess around with apps in system partition.
+ if ((flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
+ e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
+ // Delete the apk
+ Slog.w(TAG, "Cleaning up failed install of " + file);
+ file.delete();
+ }
}
}
}
@@ -4131,8 +4151,9 @@
Slog.println(priority, TAG, msg);
}
- private boolean collectCertificatesLI(PackageParser pp, PackageSetting ps,
- PackageParser.Package pkg, File srcFile, int parseFlags) {
+ private void collectCertificatesLI(PackageParser pp, PackageSetting ps,
+ PackageParser.Package pkg, File srcFile, int parseFlags)
+ throws PackageManagerException {
if (ps != null
&& ps.codePath.equals(srcFile)
&& ps.timeStamp == srcFile.lastModified()
@@ -4145,11 +4166,14 @@
// if the package appears to be unchanged.
pkg.mSignatures = ps.signatures.mSignatures;
KeySetManagerService ksms = mSettings.mKeySetManagerService;
- pkg.mSigningKeys = ksms.getPublicKeysFromKeySet(mSigningKeySetId);
- return true;
+ synchronized (mPackages) {
+ pkg.mSigningKeys = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId);
+ }
+ return;
}
- Slog.w(TAG, "PackageSetting for " + ps.name + " is missing signatures. Collecting certs again to recover them.");
+ Slog.w(TAG, "PackageSetting for " + ps.name
+ + " is missing signatures. Collecting certs again to recover them.");
} else {
Log.i(TAG, srcFile.toString() + " changed; collecting certs");
}
@@ -4158,20 +4182,17 @@
pp.collectCertificates(pkg, parseFlags);
pp.collectManifestDigest(pkg);
} catch (PackageParserException e) {
- Slog.e(TAG, "Failed during collect: " + e);
- mLastScanError = e.error;
- return false;
+ throw new PackageManagerException(e.error, "Failed to collect certificates for "
+ + pkg.packageName + ": " + e.getMessage());
}
- return true;
}
/*
* Scan a package and return the newly parsed package.
* Returns null in case of errors and the error code is stored in mLastScanError
*/
- private PackageParser.Package scanPackageLI(File scanFile,
- int parseFlags, int scanMode, long currentTime, UserHandle user, String abiOverride) {
- mLastScanError = PackageManager.INSTALL_SUCCEEDED;
+ private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanMode,
+ long currentTime, UserHandle user, String abiOverride) throws PackageManagerException {
if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile);
parseFlags |= mDefParseFlags;
PackageParser pp = new PackageParser();
@@ -4187,9 +4208,8 @@
try {
pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {
- Slog.e(TAG, "Failed during scan: " + e);
- mLastScanError = e.error;
- return null;
+ throw new PackageManagerException(e.error,
+ "Failed to scan " + scanFile + ": " + e.getMessage());
}
PackageSetting ps = null;
@@ -4241,8 +4261,7 @@
}
}
updatedPkg.pkg = pkg;
- mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE, null);
} else {
// The current app on the system partition is better than
// what we have updated to on the data partition; switch
@@ -4285,11 +4304,9 @@
parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
}
}
+
// Verify certificates against what was last scanned
- if (!collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags)) {
- Slog.w(TAG, "Failed verifying certificates for package:" + pkg.packageName);
- return null;
- }
+ collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);
/*
* A new system app appeared, but we already had a non-system one of the
@@ -4401,7 +4418,8 @@
return processName;
}
- private boolean verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg) {
+ private void verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg)
+ throws PackageManagerException {
if (pkgSetting.signatures.mSignatures != null) {
// Already existing package. Make sure signatures match
boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
@@ -4411,10 +4429,9 @@
== PackageManager.SIGNATURE_MATCH;
}
if (!match) {
- Slog.e(TAG, "Package " + pkg.packageName
- + " signatures do not match the previously installed version; ignoring!");
- mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
- return false;
+ throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
+ + pkg.packageName + " signatures do not match the "
+ + "previously installed version; ignoring!");
}
}
@@ -4428,14 +4445,12 @@
== PackageManager.SIGNATURE_MATCH;
}
if (!match) {
- Slog.e(TAG, "Package " + pkg.packageName
+ throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+ "Package " + pkg.packageName
+ " has no signatures that match those in shared user "
+ pkgSetting.sharedUser.name + "; ignoring!");
- mLastScanError = PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
- return false;
}
}
- return true;
}
/**
@@ -4617,56 +4632,70 @@
}
}
- if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
- final Collection<String> paths = pkg.getAllCodePaths();
- for (String path : paths) {
- for (String instructionSet : instructionSets) {
- try {
- boolean isDexOptNeededInternal = DexFile.isDexOptNeededInternal(path,
- pkg.packageName, instructionSet, defer);
- // There are three basic cases here:
- // 1.) we need to dexopt, either because we are forced or it is needed
- // 2.) we are defering a needed dexopt
- // 3.) we are skipping an unneeded dexopt
- if (forceDex || (!defer && isDexOptNeededInternal)) {
- Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName);
- final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg),
- pkg.packageName, instructionSet);
- // Note that we ran dexopt, since rerunning will
- // probably just result in an error again.
+ if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) {
+ return DEX_OPT_SKIPPED;
+ }
+
+ final Collection<String> paths = pkg.getAllCodePaths();
+ boolean performedDexOpt = false;
+ // There are three basic cases here:
+ // 1.) we need to dexopt, either because we are forced or it is needed
+ // 2.) we are defering a needed dexopt
+ // 3.) we are skipping an unneeded dexopt
+ for (String path : paths) {
+ for (String instructionSet : instructionSets) {
+ try {
+ final boolean isDexOptNeeded = DexFile.isDexOptNeededInternal(path,
+ pkg.packageName, instructionSet, defer);
+ if (forceDex || (!defer && isDexOptNeeded)) {
+ Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName + " isa=" + instructionSet);
+ final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+ final int ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg),
+ pkg.packageName, instructionSet);
+
+ if (ret < 0) {
+ // Don't bother running dexopt again if we failed, it will probably
+ // just result in an error again. Also, don't bother dexopting for other
+ // paths & ISAs.
pkg.mDexOptNeeded = false;
- if (ret < 0) {
- return DEX_OPT_FAILED;
- }
- return DEX_OPT_PERFORMED;
+ return DEX_OPT_FAILED;
+ } else {
+ performedDexOpt = true;
}
- if (defer && isDexOptNeededInternal) {
- if (mDeferredDexOpt == null) {
- mDeferredDexOpt = new HashSet<PackageParser.Package>();
- }
- mDeferredDexOpt.add(pkg);
- return DEX_OPT_DEFERRED;
- }
- pkg.mDexOptNeeded = false;
- return DEX_OPT_SKIPPED;
- } catch (FileNotFoundException e) {
- Slog.w(TAG, "Apk not found for dexopt: " + path);
- return DEX_OPT_FAILED;
- } catch (IOException e) {
- Slog.w(TAG, "IOException reading apk: " + path, e);
- return DEX_OPT_FAILED;
- } catch (StaleDexCacheError e) {
- Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
- return DEX_OPT_FAILED;
- } catch (Exception e) {
- Slog.w(TAG, "Exception when doing dexopt : ", e);
- return DEX_OPT_FAILED;
}
+
+ // We're deciding to defer a needed dexopt. Don't bother dexopting for other
+ // paths and instruction sets. We'll deal with them all together when we process
+ // our list of deferred dexopts.
+ if (defer && isDexOptNeeded) {
+ if (mDeferredDexOpt == null) {
+ mDeferredDexOpt = new HashSet<PackageParser.Package>();
+ }
+ mDeferredDexOpt.add(pkg);
+ return DEX_OPT_DEFERRED;
+ }
+ } catch (FileNotFoundException e) {
+ Slog.w(TAG, "Apk not found for dexopt: " + path);
+ return DEX_OPT_FAILED;
+ } catch (IOException e) {
+ Slog.w(TAG, "IOException reading apk: " + path, e);
+ return DEX_OPT_FAILED;
+ } catch (StaleDexCacheError e) {
+ Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
+ return DEX_OPT_FAILED;
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception when doing dexopt : ", e);
+ return DEX_OPT_FAILED;
}
}
}
- return DEX_OPT_SKIPPED;
+
+ // If we've gotten here, we're sure that no error occurred and that we haven't
+ // deferred dex-opt. We've either dex-opted one more paths or instruction sets or
+ // we've skipped all of them because they are up to date. In both cases this
+ // package doesn't need dexopt any longer.
+ pkg.mDexOptNeeded = false;
+ return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
}
private static String[] getAppDexInstructionSets(ApplicationInfo info) {
@@ -4687,9 +4716,12 @@
private static String[] getAppDexInstructionSets(PackageSetting ps) {
if (ps.primaryCpuAbiString != null) {
if (ps.secondaryCpuAbiString != null) {
- return new String[] { ps.primaryCpuAbiString, ps.secondaryCpuAbiString };
+ return new String[] {
+ VMRuntime.getInstructionSet(ps.primaryCpuAbiString),
+ VMRuntime.getInstructionSet(ps.secondaryCpuAbiString) };
} else {
- return new String[] { ps.primaryCpuAbiString };
+ return new String[] {
+ VMRuntime.getInstructionSet(ps.primaryCpuAbiString) };
}
}
@@ -4815,8 +4847,8 @@
}
}
- private boolean updateSharedLibrariesLPw(PackageParser.Package pkg,
- PackageParser.Package changingLib) {
+ private void updateSharedLibrariesLPw(PackageParser.Package pkg,
+ PackageParser.Package changingLib) throws PackageManagerException {
// We might be upgrading from a version of the platform that did not
// provide per-package native library directories for system apps.
// Fix that up here.
@@ -4833,11 +4865,9 @@
for (int i=0; i<N; i++) {
final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesLibraries.get(i));
if (file == null) {
- Slog.e(TAG, "Package " + pkg.packageName
- + " requires unavailable shared library "
+ throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
+ "Package " + pkg.packageName + " requires unavailable shared library "
+ pkg.usesLibraries.get(i) + "; failing!");
- mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
- return false;
}
addSharedLibraryLPw(usesLibraryFiles, file, changingLib);
}
@@ -4859,7 +4889,6 @@
pkg.usesLibraryFiles = null;
}
}
- return true;
}
private static boolean hasString(List<String> list, List<String> which) {
@@ -4878,7 +4907,11 @@
private void updateAllSharedLibrariesLPw() {
for (PackageParser.Package pkg : mPackages.values()) {
- updateSharedLibrariesLPw(pkg, null);
+ try {
+ updateSharedLibrariesLPw(pkg, null);
+ } catch (PackageManagerException e) {
+ Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
+ }
}
}
@@ -4892,21 +4925,25 @@
res = new ArrayList<PackageParser.Package>();
}
res.add(pkg);
- updateSharedLibrariesLPw(pkg, changingPkg);
+ try {
+ updateSharedLibrariesLPw(pkg, changingPkg);
+ } catch (PackageManagerException e) {
+ Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
+ }
}
}
return res;
}
- private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
- int parseFlags, int scanMode, long currentTime, UserHandle user, String abiOverride) {
+ private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,
+ int scanMode, long currentTime, UserHandle user, String abiOverride)
+ throws PackageManagerException {
final File scanFile = new File(pkg.codePath);
if (pkg.applicationInfo.getCodePath() == null ||
pkg.applicationInfo.getResourcePath() == null) {
// Bail out. The resource and code paths haven't been set.
- Slog.w(TAG, " Code and resource paths haven't been set correctly");
- mLastScanError = PackageManager.INSTALL_FAILED_INVALID_APK;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
+ "Code and resource paths haven't been set correctly");
}
if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
@@ -4929,8 +4966,8 @@
Slog.w(TAG, "Core android package being redefined. Skipping.");
Slog.w(TAG, " file=" + scanFile);
Slog.w(TAG, "*************************************************");
- mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+ "Core android package being redefined. Skipping.");
}
// Set up information for our fall-back user intent resolution activity.
@@ -4966,10 +5003,9 @@
if (mPackages.containsKey(pkg.packageName)
|| mSharedLibraries.containsKey(pkg.packageName)) {
- Slog.w(TAG, "Application package " + pkg.packageName
+ throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
+ "Application package " + pkg.packageName
+ " already installed. Skipping duplicate.");
- mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
- return null;
}
// Initialize package source and resource directories
@@ -4991,10 +5027,9 @@
if (pkg.mSharedUserId != null) {
suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, true);
if (suid == null) {
- Slog.w(TAG, "Creating application package " + pkg.packageName
+ throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+ "Creating application package " + pkg.packageName
+ " for shared user failed");
- mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- return null;
}
if (DEBUG_PACKAGE_SCANNING) {
if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
@@ -5066,11 +5101,10 @@
pkg.applicationInfo.secondaryCpuAbi,
pkg.applicationInfo.flags, user, false);
if (pkgSetting == null) {
- Slog.w(TAG, "Creating application package " + pkg.packageName + " failed");
- mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+ "Creating application package " + pkg.packageName + " failed");
}
-
+
if (pkgSetting.origPackage != null) {
// If we are first transitioning from an original package,
// fix up the new package's name now. We need to do this after
@@ -5105,9 +5139,7 @@
// are the only ones that can fail an install due to this. We
// will take care of the system apps by updating all of their
// library paths after the scan is done.
- if (!updateSharedLibrariesLPw(pkg, null)) {
- return null;
- }
+ updateSharedLibrariesLPw(pkg, null);
}
if (mFoundPolicyFile) {
@@ -5117,9 +5149,11 @@
pkg.applicationInfo.uid = pkgSetting.appId;
pkg.mExtras = pkgSetting;
if (!pkgSetting.keySetData.isUsingUpgradeKeySets() || pkgSetting.sharedUser != null) {
- if (!verifySignaturesLP(pkgSetting, pkg)) {
- if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
- return null;
+ try {
+ verifySignaturesLP(pkgSetting, pkg);
+ } catch (PackageManagerException e) {
+ if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+ throw e;
}
// The signature has changed, but this package is in the system
// image... let's recover!
@@ -5132,9 +5166,10 @@
if (pkgSetting.sharedUser != null) {
if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
- Log.w(TAG, "Signature mismatch for shared user : " + pkgSetting.sharedUser);
- mLastScanError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
- return null;
+ throw new PackageManagerException(
+ INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+ "Signature mismatch for shared user : "
+ + pkgSetting.sharedUser);
}
}
// File a report about this.
@@ -5144,10 +5179,9 @@
}
} else {
if (!checkUpgradeKeySetLP(pkgSetting, pkg)) {
- Slog.e(TAG, "Package " + pkg.packageName
- + " upgrade keys do not match the previously installed version; ");
- mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
+ + pkg.packageName + " upgrade keys do not match the "
+ + "previously installed version");
} else {
// signatures may have changed as result of upgrade
pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -5167,13 +5201,14 @@
for (int j = 0; j < names.length; j++) {
if (mProvidersByAuthority.containsKey(names[j])) {
PackageParser.Provider other = mProvidersByAuthority.get(names[j]);
- Slog.w(TAG, "Can't install because provider name " + names[j] +
- " (in package " + pkg.applicationInfo.packageName +
- ") is already used by "
- + ((other != null && other.getComponentName() != null)
- ? other.getComponentName().getPackageName() : "?"));
- mLastScanError = PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER;
- return null;
+ final String otherPackageName =
+ ((other != null && other.getComponentName() != null) ?
+ other.getComponentName().getPackageName() : "?");
+ throw new PackageManagerException(
+ INSTALL_FAILED_CONFLICTING_PROVIDER,
+ "Can't install because provider name " + names[j]
+ + " (in package " + pkg.applicationInfo.packageName
+ + ") is already used by " + otherPackageName);
}
}
}
@@ -5269,8 +5304,8 @@
msg = prefix + pkg.packageName
+ " could not have data directory re-created after delete.";
reportSettingsProblem(Log.WARN, msg);
- mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- return null;
+ throw new PackageManagerException(
+ INSTALL_FAILED_INSUFFICIENT_STORAGE, msg);
}
}
if (!recovered) {
@@ -5279,8 +5314,8 @@
} else if (!recovered) {
// If we allow this install to proceed, we will be broken.
// Abort, abort!
- mLastScanError = PackageManager.INSTALL_FAILED_UID_CHANGED;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_UID_CHANGED,
+ "scanPackageLI");
}
if (!recovered) {
pkg.applicationInfo.dataDir = "/mismatched_uid/settings_"
@@ -5319,9 +5354,8 @@
pkg.applicationInfo.seinfo);
if (ret < 0) {
// Error from installer
- Slog.w(TAG, "Unable to create data dirs [errorCode=" + ret + "]");
- mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
+ "Unable to create data dirs [errorCode=" + ret + "]");
}
if (dataPath.exists()) {
@@ -5395,9 +5429,9 @@
}
if (abi32 < 0 && abi32 != PackageManager.NO_NATIVE_LIBRARIES) {
- Slog.w(TAG, "Error unpackaging 32 bit native libs for multiarch app, errorCode=" + abi32);
- mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Error unpackaging 32 bit native libs for multiarch app, errorCode="
+ + abi32);
}
if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
@@ -5410,12 +5444,11 @@
}
if (abi64 < 0 && abi64 != PackageManager.NO_NATIVE_LIBRARIES) {
- Slog.w(TAG, "Error unpackaging 64 bit native libs for multiarch app, errorCode=" + abi32);
- mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Error unpackaging 64 bit native libs for multiarch app, errorCode="
+ + abi32);
}
-
if (abi64 >= 0) {
pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[abi64];
}
@@ -5450,9 +5483,8 @@
}
if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
- Slog.w(TAG, "Error unpackaging native libs for app, errorCode=" + copyRet);
- mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Error unpackaging native libs for app, errorCode=" + copyRet);
}
if (copyRet >= 0) {
@@ -5480,10 +5512,8 @@
final String nativeLibPath = pkg.applicationInfo.nativeLibraryDir;
for (int userId : userIds) {
if (mInstaller.linkNativeLibraryDirectory(pkg.packageName, nativeLibPath, userId) < 0) {
- Slog.w(TAG, "Failed linking native library dir (user=" + userId
- + ")");
- mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed linking native library dir (user=" + userId + ")");
}
}
}
@@ -5516,8 +5546,8 @@
// code and package path correct.
if (!adjustCpuAbisForSharedUserLPw(pkgSetting.sharedUser.packages,
pkg, forceDex, (scanMode & SCAN_DEFER_DEX) != 0)) {
- mLastScanError = PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_CPU_ABI_INCOMPATIBLE,
+ "scanPackageLI");
}
}
@@ -5528,8 +5558,7 @@
removeDataDirsLI(pkg.packageName);
}
- mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_DEXOPT, "scanPackageLI");
}
}
@@ -5608,8 +5637,8 @@
removeDataDirsLI(pkg.packageName);
}
- mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_DEXOPT,
+ "scanPackageLI failed to dexopt clientLibPkgs");
}
}
}
@@ -5687,19 +5716,19 @@
KeySetManagerService ksms = mSettings.mKeySetManagerService;
try {
// Old KeySetData no longer valid.
- ksms.removeAppKeySetData(pkg.packageName);
- ksms.addSigningKeySetToPackage(pkg.packageName, pkg.mSigningKeys);
+ ksms.removeAppKeySetDataLPw(pkg.packageName);
+ ksms.addSigningKeySetToPackageLPw(pkg.packageName, pkg.mSigningKeys);
if (pkg.mKeySetMapping != null) {
for (Map.Entry<String, ArraySet<PublicKey>> entry :
pkg.mKeySetMapping.entrySet()) {
if (entry.getValue() != null) {
- ksms.addDefinedKeySetToPackage(pkg.packageName,
+ ksms.addDefinedKeySetToPackageLPw(pkg.packageName,
entry.getValue(), entry.getKey());
}
}
if (pkg.mUpgradeKeySets != null) {
for (String upgradeAlias : pkg.mUpgradeKeySets) {
- ksms.addUpgradeKeySetToPackage(pkg.packageName, upgradeAlias);
+ ksms.addUpgradeKeySetToPackageLPw(pkg.packageName, upgradeAlias);
}
}
}
@@ -5991,8 +6020,8 @@
map.put(pkg.packageName, pkg);
PackageParser.Package orig = mPackages.get(pkg.mOverlayTarget);
if (orig != null && !createIdmapForPackagePairLI(orig, pkg)) {
- mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
- return null;
+ throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+ "scanPackageLI failed to createIdmap");
}
}
} else if (mOverlays.containsKey(pkg.packageName) &&
@@ -6287,7 +6316,7 @@
Slog.e(TAG, "Package: " + pkg + " has multiple bundled libs, but is not multiarch.");
}
- if (VMRuntime.is64BitAbi(getPreferredInstructionSet())) {
+ if (VMRuntime.is64BitInstructionSet(getPreferredInstructionSet())) {
pkg.applicationInfo.primaryCpuAbi = Build.SUPPORTED_64_BIT_ABIS[0];
pkg.applicationInfo.secondaryCpuAbi = Build.SUPPORTED_32_BIT_ABIS[0];
} else {
@@ -6343,8 +6372,7 @@
subDir = nativeLibraryRoot;
}
- int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle,
- subDir, Build.SUPPORTED_ABIS[abi]);
+ int copyRet = NativeLibraryHelper.copyNativeBinariesIfNeededLI(handle, subDir, abiList[abi]);
if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
return copyRet;
}
@@ -7672,9 +7700,14 @@
flags |= PackageParser.PARSE_IS_PRIVILEGED;
}
}
- p = scanPackageLI(fullPath, flags,
- SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME,
- System.currentTimeMillis(), UserHandle.ALL, null);
+ try {
+ p = scanPackageLI(fullPath, flags,
+ SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME,
+ System.currentTimeMillis(), UserHandle.ALL, null);
+ } catch (PackageManagerException e) {
+ Slog.w(TAG, "Failed to scan " + fullPath + ": " + e.getMessage());
+ p = null;
+ }
if (p != null) {
/*
* TODO this seems dangerous as the package may have
@@ -7730,7 +7763,7 @@
if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) {
try {
if (observer != null) {
- observer.packageInstalled("", null, INSTALL_FAILED_USER_RESTRICTED);
+ observer.packageInstalled("", null, INSTALL_FAILED_USER_RESTRICTED, null);
}
} catch (RemoteException re) {
}
@@ -7763,7 +7796,7 @@
}
void installStage(String packageName, File stageDir, IPackageInstallObserver2 observer,
- PackageInstallerParams params, String installerPackageName, int installerUid,
+ InstallSessionParams params, String installerPackageName, int installerUid,
UserHandle user) {
final VerificationParams verifParams = new VerificationParams(null, params.originatingUri,
params.referrerUri, installerUid, null);
@@ -9823,8 +9856,15 @@
int[] newUsers;
PackageParser.Package pkg;
int returnCode;
+ String returnMsg;
PackageRemovedInfo removedInfo;
+ public void setError(int code, String msg) {
+ returnCode = code;
+ returnMsg = msg;
+ Slog.w(TAG, msg);
+ }
+
// In some error cases we want to convey more info back to the observer
String origPackage;
String origPermission;
@@ -9847,29 +9887,23 @@
// it has been renamed to an older name. The package we
// are trying to install should be installed as an update to
// the existing one, but that has not been requested, so bail.
- Slog.w(TAG, "Attempt to re-install " + pkgName
+ res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName
+ " without first uninstalling package running as "
+ mSettings.mRenamedPackages.get(pkgName));
- res.returnCode = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
return;
}
if (mPackages.containsKey(pkgName) || mAppDirs.containsKey(pkg.codePath)) {
// Don't allow installation over an existing package with the same name.
- Slog.w(TAG, "Attempt to re-install " + pkgName
+ res.setError(INSTALL_FAILED_ALREADY_EXISTS, "Attempt to re-install " + pkgName
+ " without first uninstalling.");
- res.returnCode = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
return;
}
}
- mLastScanError = PackageManager.INSTALL_SUCCEEDED;
- PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,
- System.currentTimeMillis(), user, abiOverride);
- if (newPackage == null) {
- Slog.w(TAG, "Package couldn't be installed in " + pkg.codePath);
- if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
- res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
- }
- } else {
+
+ try {
+ PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,
+ System.currentTimeMillis(), user, abiOverride);
+
updateSettingsLI(newPackage, installerPackageName, null, null, res);
// delete the partially installed application. the data directory will have to be
// restored if it was already existing
@@ -9882,6 +9916,10 @@
dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0,
res.removedInfo, true);
}
+
+ } catch (PackageManagerException e) {
+ res.setError(e.error,
+ "Package couldn't be installed in " + pkg.codePath + ": " + e.getMessage());
}
}
@@ -9890,14 +9928,9 @@
// required keys.
long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
KeySetManagerService ksms = mSettings.mKeySetManagerService;
- Set<Long> newSigningKeyIds = new ArraySet<Long>();
- for (PublicKey pk : newPkg.mSigningKeys) {
- newSigningKeyIds.add(ksms.getIdForPublicKey(pk));
- }
- //remove PUBLIC_KEY_NOT_FOUND, although not necessary
- newSigningKeyIds.remove(ksms.PUBLIC_KEY_NOT_FOUND);
for (int i = 0; i < upgradeKeySets.length; i++) {
- if (newSigningKeyIds.containsAll(ksms.mKeySetMapping.get(upgradeKeySets[i]))) {
+ Set<PublicKey> upgradeSet = ksms.getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
+ if (newPkg.mSigningKeys.containsAll(upgradeSet)) {
return true;
}
}
@@ -9921,15 +9954,15 @@
// default to original signature matching
if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
!= PackageManager.SIGNATURE_MATCH) {
- Slog.w(TAG, "New package has a different signature: " + pkgName);
- res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+ res.setError(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+ "New package has a different signature: " + pkgName);
return;
}
} else {
if(!checkUpgradeKeySetLP(ps, pkg)) {
- Slog.w(TAG, "New package not signed by keys specified by upgrade-keysets: "
- + pkgName);
- res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+ res.setError(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+ "New package not signed by keys specified by upgrade-keysets: "
+ + pkgName);
return;
}
}
@@ -9957,7 +9990,6 @@
PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
int[] allUsers, boolean[] perUserInstalled,
String installerPackageName, PackageInstalledInfo res, String abiOverride) {
- PackageParser.Package newPackage = null;
String pkgName = deletedPackage.packageName;
boolean deletedPkg = true;
boolean updatedSettings = false;
@@ -9975,21 +10007,18 @@
if (!deletePackageLI(pkgName, null, true, null, null, PackageManager.DELETE_KEEP_DATA,
res.removedInfo, true)) {
// If the existing package wasn't successfully deleted
- res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
+ res.setError(INSTALL_FAILED_REPLACE_COULDNT_DELETE, "replaceNonSystemPackageLI");
deletedPkg = false;
} else {
// Successfully deleted the old package. Now proceed with re-installation
- mLastScanError = PackageManager.INSTALL_SUCCEEDED;
- newPackage = scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_TIME,
- System.currentTimeMillis(), user, abiOverride);
- if (newPackage == null) {
- Slog.w(TAG, "Package couldn't be installed in " + pkg.codePath);
- if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
- res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
- }
- } else {
+ try {
+ final PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags,
+ scanMode | SCAN_UPDATE_TIME, System.currentTimeMillis(), user, abiOverride);
updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res);
updatedSettings = true;
+ } catch (PackageManagerException e) {
+ res.setError(e.error,
+ "Package couldn't be installed in " + pkg.codePath + ": " + e.getMessage());
}
}
@@ -10017,9 +10046,12 @@
(oldOnSd ? PackageParser.PARSE_ON_SDCARD : 0);
int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE
| SCAN_UPDATE_TIME;
- if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode,
- origUpdateTime, null, null) == null) {
- Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade");
+ try {
+ scanPackageLI(restoreFile, oldParseFlags, oldScanMode, origUpdateTime, null,
+ null);
+ } catch (PackageManagerException e) {
+ Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade: "
+ + e.getMessage());
return;
}
// Restore of old package succeeded. Update permissions.
@@ -10041,7 +10073,6 @@
String installerPackageName, PackageInstalledInfo res, String abiOverride) {
if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
+ ", old=" + deletedPackage);
- PackageParser.Package newPackage = null;
boolean updatedSettings = false;
parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING |
PackageParser.PARSE_IS_SYSTEM;
@@ -10049,9 +10080,9 @@
parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
}
String packageName = deletedPackage.packageName;
- res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
if (packageName == null) {
- Slog.w(TAG, "Attempt to delete null packageName.");
+ res.setError(INSTALL_FAILED_REPLACE_COULDNT_DELETE,
+ "Attempt to delete null packageName.");
return;
}
PackageParser.Package oldPkg;
@@ -10062,7 +10093,8 @@
oldPkgSetting = mSettings.mPackages.get(packageName);
if((oldPkg == null) || (oldPkg.applicationInfo == null) ||
(oldPkgSetting == null)) {
- Slog.w(TAG, "Couldn't find package:"+packageName+" information");
+ res.setError(INSTALL_FAILED_REPLACE_COULDNT_DELETE,
+ "Couldn't find package:" + packageName + " information");
return;
}
}
@@ -10091,25 +10123,22 @@
}
// Successfully disabled the old package. Now proceed with re-installation
- res.returnCode = mLastScanError = PackageManager.INSTALL_SUCCEEDED;
+ res.returnCode = PackageManager.INSTALL_SUCCEEDED;
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
- newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0, user, abiOverride);
- if (newPackage == null) {
- Slog.w(TAG, "Package couldn't be installed in " + pkg.codePath);
- if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
- res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
- }
- } else {
+
+ PackageParser.Package newPackage = null;
+ try {
+ newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0, user, abiOverride);
if (newPackage.mExtras != null) {
- final PackageSetting newPkgSetting = (PackageSetting)newPackage.mExtras;
+ final PackageSetting newPkgSetting = (PackageSetting) newPackage.mExtras;
newPkgSetting.firstInstallTime = oldPkgSetting.firstInstallTime;
newPkgSetting.lastUpdateTime = System.currentTimeMillis();
// is the update attempting to change shared user? that isn't going to work...
if (oldPkgSetting.sharedUser != newPkgSetting.sharedUser) {
- Slog.w(TAG, "Forbidding shared user change from " + oldPkgSetting.sharedUser
+ res.setError(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+ "Forbidding shared user change from " + oldPkgSetting.sharedUser
+ " to " + newPkgSetting.sharedUser);
- res.returnCode = PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
updatedSettings = true;
}
}
@@ -10118,6 +10147,10 @@
updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res);
updatedSettings = true;
}
+
+ } catch (PackageManagerException e) {
+ res.setError(e.error,
+ "Package couldn't be installed in " + pkg.codePath + ": " + e.getMessage());
}
if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
@@ -10127,7 +10160,12 @@
removeInstalledPackageLI(newPackage, true);
}
// Add back the old system package
- scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0, user, null);
+ try {
+ scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0, user,
+ null);
+ } catch (PackageManagerException e) {
+ Slog.e(TAG, "Failed to restore original package: " + e.getMessage());
+ }
// Restore the old system information in Settings
synchronized(mPackages) {
if (updatedSettings) {
@@ -10250,15 +10288,14 @@
try {
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
} catch (PackageParserException e) {
- Slog.e(TAG, "Failed during install: " + e);
- res.returnCode = e.error;
+ res.setError(e.error, "Failed parse during installPackageLI: " + e.getMessage());
return;
}
String pkgName = res.name = pkg.packageName;
if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {
- res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;
+ res.setError(INSTALL_FAILED_TEST_ONLY, "installPackageLI");
return;
}
}
@@ -10267,8 +10304,7 @@
pp.collectCertificates(pkg, parseFlags);
pp.collectManifestDigest(pkg);
} catch (PackageParserException e) {
- Slog.e(TAG, "Failed during install: " + e);
- res.returnCode = e.error;
+ res.setError(e.error, "Failed collect during installPackageLI: " + e.getMessage());
return;
}
@@ -10282,7 +10318,7 @@
}
if (!args.manifestDigest.equals(pkg.manifestDigest)) {
- res.returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+ res.setError(INSTALL_FAILED_PACKAGE_CHANGED, "Manifest digest changed");
return;
}
} else if (DEBUG_INSTALL) {
@@ -10310,10 +10346,9 @@
// install to proceed; we fail the install on all other permission
// redefinitions.
if (!bp.sourcePackage.equals("android")) {
- Slog.w(TAG, "Package " + pkg.packageName
- + " attempting to redeclare permission " + perm.info.name
- + " already owned by " + bp.sourcePackage);
- res.returnCode = PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION;
+ res.setError(INSTALL_FAILED_DUPLICATE_PERMISSION, "Package "
+ + pkg.packageName + " attempting to redeclare permission "
+ + perm.info.name + " already owned by " + bp.sourcePackage);
res.origPermission = perm.info.name;
res.origPackage = bp.sourcePackage;
return;
@@ -10363,13 +10398,13 @@
if (systemApp && onSd) {
// Disable updates to system apps on sdcard
- Slog.w(TAG, "Cannot install updates to system apps on sdcard");
- res.returnCode = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+ res.setError(INSTALL_FAILED_INVALID_INSTALL_LOCATION,
+ "Cannot install updates to system apps on sdcard");
return;
}
if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
- res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+ res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
return;
}
@@ -10490,6 +10525,26 @@
return;
}
+ boolean blocked = false;
+ if ((flags & PackageManager.DELETE_ALL_USERS) != 0) {
+ int[] users = sUserManager.getUserIds();
+ for (int i = 0; i < users.length; ++i) {
+ if (getBlockUninstallForUser(packageName, users[i])) {
+ blocked = true;
+ break;
+ }
+ }
+ } else {
+ blocked = getBlockUninstallForUser(packageName, userId);
+ }
+ if (blocked) {
+ try {
+ observer.packageDeleted(packageName, PackageManager.DELETE_FAILED_OWNER_BLOCKED);
+ } catch (RemoteException re) {
+ }
+ return;
+ }
+
if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageAsUser: pkg=" + packageName + " user=" + userId);
// Queue up an async operation since the package deletion may take a little while.
mHandler.post(new Runnable() {
@@ -10671,7 +10726,7 @@
if (deletedPs != null) {
if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
if (outInfo != null) {
- mSettings.mKeySetManagerService.removeAppKeySetData(packageName);
+ mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName);
outInfo.removedAppId = mSettings.removePackageLPw(packageName);
}
if (deletedPs != null) {
@@ -10781,14 +10836,16 @@
if (locationIsPrivileged(disabledPs.codePath)) {
parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
}
- PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath,
- parseFlags, SCAN_MONITOR | SCAN_NO_PATHS, 0, null, null);
- if (newPkg == null) {
- Slog.w(TAG, "Failed to restore system package:" + newPs.name
- + " with error:" + mLastScanError);
+ final PackageParser.Package newPkg;
+ try {
+ newPkg = scanPackageLI(disabledPs.codePath, parseFlags, SCAN_MONITOR | SCAN_NO_PATHS, 0,
+ null, null);
+ } catch (PackageManagerException e) {
+ Slog.w(TAG, "Failed to restore system package:" + newPs.name + ": " + e.getMessage());
return false;
}
+
// writer
synchronized (mPackages) {
PackageSetting ps = mSettings.mPackages.get(newPkg.packageName);
@@ -11960,30 +12017,19 @@
static class DumpState {
public static final int DUMP_LIBS = 1 << 0;
-
public static final int DUMP_FEATURES = 1 << 1;
-
public static final int DUMP_RESOLVERS = 1 << 2;
-
public static final int DUMP_PERMISSIONS = 1 << 3;
-
public static final int DUMP_PACKAGES = 1 << 4;
-
public static final int DUMP_SHARED_USERS = 1 << 5;
-
public static final int DUMP_MESSAGES = 1 << 6;
-
public static final int DUMP_PROVIDERS = 1 << 7;
-
public static final int DUMP_VERIFIERS = 1 << 8;
-
public static final int DUMP_PREFERRED = 1 << 9;
-
public static final int DUMP_PREFERRED_XML = 1 << 10;
-
public static final int DUMP_KEYSETS = 1 << 11;
-
public static final int DUMP_VERSION = 1 << 12;
+ public static final int DUMP_INSTALLS = 1 << 13;
public static final int OPTION_SHOW_FILTERS = 1 << 0;
@@ -12087,6 +12133,7 @@
pw.println(" version: print database version info");
pw.println(" write: write current settings now");
pw.println(" <package.name>: info about given package");
+ pw.println(" installs: details about install sessions");
return;
} else if ("--checkin".equals(opt)) {
checkin = true;
@@ -12143,6 +12190,8 @@
pw.println("Settings written.");
return;
}
+ } else if ("installs".equals(cmd)) {
+ dumpState.setDump(DumpState.DUMP_INSTALLS);
}
}
@@ -12347,7 +12396,7 @@
}
if (!checkin && dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
- mSettings.mKeySetManagerService.dump(pw, packageName, dumpState);
+ mSettings.mKeySetManagerService.dumpLPr(pw, packageName, dumpState);
}
if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) {
@@ -12358,9 +12407,13 @@
mSettings.dumpSharedUsersLPr(pw, packageName, dumpState);
}
+ if (!checkin && dumpState.isDumping(DumpState.DUMP_INSTALLS)) {
+ if (dumpState.onTitlePrinted()) pw.println();
+ mInstallerService.dump(new IndentingPrintWriter(pw, " ", 120));
+ }
+
if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
- if (dumpState.onTitlePrinted())
- pw.println();
+ if (dumpState.onTitlePrinted()) pw.println();
mSettings.dumpReadMessagesLPr(pw, dumpState);
pw.println();
@@ -12652,8 +12705,12 @@
doGc = true;
synchronized (mInstallLock) {
- final PackageParser.Package pkg = scanPackageLI(new File(codePath), parseFlags,
- 0, 0, null, null);
+ PackageParser.Package pkg = null;
+ try {
+ pkg = scanPackageLI(new File(codePath), parseFlags, 0, 0, null, null);
+ } catch (PackageManagerException e) {
+ Slog.w(TAG, "Failed to scan " + codePath + ": " + e.getMessage());
+ }
// Scan the package
if (pkg != null) {
/*
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 64288a5..3882769 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -299,6 +299,9 @@
if (enabledOnly && !profile.isEnabled()) {
continue;
}
+ if (mRemovingUserIds.get(profile.id)) {
+ continue;
+ }
users.add(profile);
}
return users;
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6be4d4f..fac78a9 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -861,6 +861,26 @@
}
@Override
+ public void setCaptionEnabled(IBinder sessionToken, boolean enabled, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setCaptionEnabled");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .setCaptionEnabled(enabled);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in setCaptionEnabled", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void selectTrack(IBinder sessionToken, TvTrackInfo track, int userId) {
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
@@ -1020,14 +1040,13 @@
}
@Override
+ @SuppressWarnings("resource")
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- if (mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.DUMP)
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
- pw.println("Permission Denial: can't dump TvInputManager " +
- "from from pid=" + Binder.getCallingPid() + ", uid=" +
- Binder.getCallingUid());
+ pw.println("Permission Denial: can't dump TvInputManager from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
return;
}
@@ -1043,7 +1062,6 @@
for (int i = 0; i < mUserStates.size(); i++) {
int userId = mUserStates.keyAt(i);
UserState userState = getUserStateLocked(userId);
-
pw.println("UserState (" + userId + "):");
pw.increaseIndent();
diff --git a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
index 5bafb52..87626d0 100644
--- a/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -52,6 +52,7 @@
static jmethodID method_reportGeofenceRemoveStatus;
static jmethodID method_reportGeofencePauseStatus;
static jmethodID method_reportGeofenceResumeStatus;
+static jmethodID method_reportMeasurementData;
static const GpsInterface* sGpsInterface = NULL;
static const GpsXtraInterface* sGpsXtraInterface = NULL;
@@ -60,6 +61,7 @@
static const GpsDebugInterface* sGpsDebugInterface = NULL;
static const AGpsRilInterface* sAGpsRilInterface = NULL;
static const GpsGeofencingInterface* sGpsGeofencingInterface = NULL;
+static const GpsMeasurementInterface* sGpsMeasurementInterface = NULL;
// temporary storage for GPS callbacks
static GpsSvStatus sGpsSvStatus;
@@ -441,6 +443,10 @@
"(II)V");
method_reportGeofencePauseStatus = env->GetMethodID(clazz,"reportGeofencePauseStatus",
"(II)V");
+ method_reportMeasurementData = env->GetMethodID(
+ clazz,
+ "reportMeasurementData",
+ "(Landroid/location/GpsMeasurementsEvent;)V");
err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
if (err == 0) {
@@ -464,6 +470,8 @@
(const AGpsRilInterface*)sGpsInterface->get_extension(AGPS_RIL_INTERFACE);
sGpsGeofencingInterface =
(const GpsGeofencingInterface*)sGpsInterface->get_extension(GPS_GEOFENCING_INTERFACE);
+ sGpsMeasurementInterface =
+ (const GpsMeasurementInterface*)sGpsInterface->get_extension(GPS_MEASUREMENT_INTERFACE);
}
}
@@ -851,42 +859,500 @@
return JNI_FALSE;
}
+static jobject translate_gps_clock(JNIEnv* env, GpsClock* clock) {
+ const char* doubleSignature = "(D)V";
+
+ jclass gpsClockClass = env->FindClass("android/location/GpsClock");
+ jmethodID gpsClockCtor = env->GetMethodID(gpsClockClass, "<init>", "()V");
+
+ jobject gpsClockObject = env->NewObject(gpsClockClass, gpsClockCtor);
+ GpsClockFlags flags = clock->flags;
+
+ if (flags & GPS_CLOCK_HAS_LEAP_SECOND) {
+ jmethodID setterMethod = env->GetMethodID(gpsClockClass, "setLeapSecond", "(S)V");
+ env->CallObjectMethod(gpsClockObject, setterMethod, clock->leap_second);
+ }
+
+ jmethodID setterMethod = env->GetMethodID(gpsClockClass, "setTimeInNs", "(J)V");
+ env->CallObjectMethod(gpsClockObject, setterMethod, clock->time_ns);
+
+ if (flags & GPS_CLOCK_HAS_TIME_UNCERTAINTY) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsClockClass,
+ "setTimeUncertaintyInNs",
+ doubleSignature);
+ env->CallObjectMethod(gpsClockObject, setterMethod, clock->time_uncertainty_ns);
+ }
+
+ if (flags & GPS_CLOCK_HAS_BIAS) {
+ jmethodID setterMethod = env->GetMethodID(gpsClockClass, "setBiasInNs", doubleSignature);
+ env->CallObjectMethod(gpsClockObject, setterMethod, clock->bias_ns);
+ }
+
+ if (flags & GPS_CLOCK_HAS_BIAS_UNCERTAINTY) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsClockClass,
+ "setBiasUncertaintyInNs",
+ doubleSignature);
+ env->CallObjectMethod(gpsClockObject, setterMethod, clock->bias_uncertainty_ns);
+ }
+
+ if (flags & GPS_CLOCK_HAS_DRIFT) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsClockClass,
+ "setDriftInNsPerSec",
+ doubleSignature);
+ env->CallObjectMethod(gpsClockObject, setterMethod, clock->drift_nsps);
+ }
+
+ if (flags & GPS_CLOCK_HAS_DRIFT_UNCERTAINTY) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsClockClass,
+ "setDriftUncertaintyInNsPerSec",
+ doubleSignature);
+ env->CallObjectMethod(gpsClockObject, setterMethod, clock->drift_uncertainty_nsps);
+ }
+
+ return gpsClockObject;
+}
+
+static jobject translate_gps_measurement(
+ JNIEnv* env,
+ GpsMeasurement* measurement,
+ uint32_t time_ns) {
+ const char* shortSignature = "(S)V";
+ const char* longSignature = "(J)V";
+ const char* floatSignature = "(F)V";
+ const char* doubleSignature = "(D)V";
+
+ jclass gpsMeasurementClass = env->FindClass("android/location/GpsMeasurement");
+ jmethodID gpsMeasurementCtor = env->GetMethodID(gpsMeasurementClass, "<init>", "()V");
+
+ jobject gpsMeasurementObject = env->NewObject(gpsMeasurementClass, gpsMeasurementCtor);
+ GpsMeasurementFlags flags = measurement->flags;
+
+
+ jmethodID prnSetterMethod = env->GetMethodID(gpsMeasurementClass, "setPrn", "(B)V");
+ env->CallObjectMethod(gpsMeasurementObject, prnSetterMethod, measurement->prn);
+
+ jmethodID localTimeSetterMethod =
+ env->GetMethodID(gpsMeasurementClass, "setLocalTimeInNs", longSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ localTimeSetterMethod,
+ time_ns + measurement->time_offset_ns);
+
+ jmethodID receivedGpsTowSetterMethod =
+ env->GetMethodID(gpsMeasurementClass, "setReceivedGpsTowInNs", longSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ receivedGpsTowSetterMethod,
+ measurement->received_gps_tow_ns);
+
+ jmethodID cn0SetterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setCn0InDbHz",
+ doubleSignature);
+ env->CallObjectMethod(gpsMeasurementObject, cn0SetterMethod, measurement->c_n0_dbhz);
+
+ jmethodID pseudorangeRateSetterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setPseudorangeRateInMetersPerSec",
+ doubleSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ pseudorangeRateSetterMethod,
+ measurement->pseudorange_rate_mpersec);
+
+ jmethodID pseudorangeRateUncertaintySetterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setPseudorangeRateUncertaintyInMetersPerSec",
+ doubleSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ pseudorangeRateUncertaintySetterMethod,
+ measurement->pseudorange_rate_uncertainty_mpersec);
+
+ jmethodID accumulatedDeltaRangeSetterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setAccumulatedDeltaRangeInMeters",
+ doubleSignature);
+ env->CallVoidMethod(
+ gpsMeasurementObject,
+ accumulatedDeltaRangeSetterMethod,
+ measurement->accumulated_delta_range_m);
+
+ jmethodID accumulatedDeltaRangeUncertaintySetterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setAccumulatedDeltaRangeUncertaintyInMeters",
+ doubleSignature);
+ env->CallVoidMethod(
+ gpsMeasurementObject,
+ accumulatedDeltaRangeUncertaintySetterMethod,
+ measurement->accumulated_delta_range_uncertainty_m);
+
+
+ if (flags & GPS_MEASUREMENT_HAS_PSEUDORANGE) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setPseudorangeInMeters",
+ doubleSignature);
+ env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->pseudorange_m);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_PSEUDORANGE_UNCERTAINTY) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setPseudorangeUncertaintyInMeters",
+ doubleSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ setterMethod,
+ measurement->pseudorange_uncertainty_m);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_CODE_PHASE) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setCodePhaseInChips",
+ doubleSignature);
+ env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->code_phase_chips);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_CODE_PHASE_UNCERTAINTY) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setCodePhaseUncertaintyInChips",
+ doubleSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ setterMethod,
+ measurement->code_phase_uncertainty_chips);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_CARRIER_FREQUENCY) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setCarrierFrequencyInHz",
+ floatSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ setterMethod,
+ measurement->carrier_frequency_hz);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_CARRIER_CYCLES) {
+ jmethodID setterMethod =
+ env->GetMethodID(gpsMeasurementClass, "setCarrierCycles", longSignature);
+ env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->carrier_cycles);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_CARRIER_PHASE) {
+ jmethodID setterMethod =
+ env->GetMethodID(gpsMeasurementClass, "setCarrierPhase", doubleSignature);
+ env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->carrier_phase);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_CARRIER_PHASE_UNCERTAINTY) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setCarrierPhaseUncertainty",
+ doubleSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ setterMethod,
+ measurement->carrier_phase_uncertainty);
+ }
+
+ jmethodID lossOfLockSetterMethod =
+ env->GetMethodID(gpsMeasurementClass, "setLossOfLock", shortSignature);
+ env->CallObjectMethod(gpsMeasurementObject, lossOfLockSetterMethod, measurement->loss_of_lock);
+
+ if (flags & GPS_MEASUREMENT_HAS_BIT_NUMBER) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setBitNumber",
+ shortSignature);
+ env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->bit_number);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_TIME_FROM_LAST_BIT) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setTimeFromLastBitInNs",
+ longSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ setterMethod,
+ measurement->time_from_last_bit_ns);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_DOPPLER_SHIFT) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setDopplerShiftInHz",
+ doubleSignature);
+ env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->doppler_shift_hz);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_DOPPLER_SHIFT_UNCERTAINTY) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setDopplerShiftUncertaintyInHz",
+ doubleSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ setterMethod,
+ measurement->doppler_shift_uncertainty_hz);
+ }
+
+ jmethodID multipathIndicatorSetterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setMultipathIndicator",
+ shortSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ multipathIndicatorSetterMethod,
+ measurement->multipath_indicator);
+
+ if (flags & GPS_MEASUREMENT_HAS_SNR) {
+ jmethodID setterMethod =
+ env->GetMethodID(gpsMeasurementClass, "setSnrInDb", doubleSignature);
+ env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->snr_db);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_ELEVATION) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setElevationInDeg",
+ doubleSignature);
+ env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->elevation_deg);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_ELEVATION_UNCERTAINTY) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setElevationUncertaintyInDeg",
+ doubleSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ setterMethod,
+ measurement->elevation_uncertainty_deg);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_AZIMUTH) {
+ jmethodID setterMethod =
+ env->GetMethodID(gpsMeasurementClass, "setAzimuthInDeg", doubleSignature);
+ env->CallObjectMethod(gpsMeasurementObject, setterMethod, measurement->azimuth_deg);
+ }
+
+ if (flags & GPS_MEASUREMENT_HAS_AZIMUTH_UNCERTAINTY) {
+ jmethodID setterMethod = env->GetMethodID(
+ gpsMeasurementClass,
+ "setAzimuthUncertaintyInDeg",
+ doubleSignature);
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ setterMethod,
+ measurement->azimuth_uncertainty_deg);
+ }
+
+ jmethodID usedInFixSetterMethod = env->GetMethodID(gpsMeasurementClass, "setUsedInFix", "(Z)V");
+ env->CallObjectMethod(
+ gpsMeasurementObject,
+ usedInFixSetterMethod,
+ (flags & GPS_MEASUREMENT_HAS_USED_IN_FIX) && measurement->used_in_fix);
+
+ return gpsMeasurementObject;
+}
+
+static jobjectArray translate_gps_measurements(JNIEnv* env, GpsData* data) {
+ size_t measurementCount = data->measurement_count;
+ if (measurementCount == 0) {
+ return NULL;
+ }
+
+ jclass gpsMeasurementClass = env->FindClass("android/location/GpsMeasurement");
+ jobjectArray gpsMeasurementArray = env->NewObjectArray(
+ measurementCount,
+ gpsMeasurementClass,
+ NULL /* initialElement */);
+
+ GpsMeasurement* gpsMeasurements = data->measurements;
+ for (uint16_t i = 0; i < measurementCount; ++i) {
+ jobject gpsMeasurement = translate_gps_measurement(
+ env,
+ &gpsMeasurements[i],
+ data->clock.time_ns);
+ env->SetObjectArrayElement(gpsMeasurementArray, i, gpsMeasurement);
+ env->DeleteLocalRef(gpsMeasurement);
+ }
+
+ env->DeleteLocalRef(gpsMeasurementClass);
+ return gpsMeasurementArray;
+}
+
+static void measurement_callback(GpsData* data) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ if (data == NULL) {
+ ALOGE("Invalid data provided to gps_measurement_callback");
+ return;
+ }
+
+ if (data->size == sizeof(GpsData)) {
+ jobject gpsClock = translate_gps_clock(env, &data->clock);
+ jobjectArray measurementArray = translate_gps_measurements(env, data);
+
+ jclass gpsMeasurementsEventClass = env->FindClass("android/location/GpsMeasurementsEvent");
+ jmethodID gpsMeasurementsEventCtor = env->GetMethodID(
+ gpsMeasurementsEventClass,
+ "<init>",
+ "(Landroid/location/GpsClock;[Landroid/location/GpsMeasurement;)V");
+
+ jobject gpsMeasurementsEvent = env->NewObject(
+ gpsMeasurementsEventClass,
+ gpsMeasurementsEventCtor,
+ gpsClock,
+ measurementArray);
+
+ env->CallVoidMethod(mCallbacksObj, method_reportMeasurementData, gpsMeasurementsEvent);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ } else {
+ ALOGE("Invalid GpsData size found in gps_measurement_callback, size=%d", data->size);
+ return;
+ }
+}
+
+GpsMeasurementCallbacks sGpsMeasurementCallbacks = {
+ sizeof(GpsMeasurementCallbacks),
+ measurement_callback,
+};
+
+static jboolean android_location_GpsLocationProvider_is_measurement_supported(
+ JNIEnv* env,
+ jobject obj) {
+ if (sGpsMeasurementInterface != NULL) {
+ return JNI_TRUE;
+ }
+ return JNI_FALSE;
+}
+
+static jboolean android_location_GpsLocationProvider_start_measurement_collection(
+ JNIEnv* env,
+ jobject obj) {
+ if (sGpsMeasurementInterface == NULL) {
+ ALOGE("Measurement interface is not available.");
+ return JNI_FALSE;
+ }
+
+ int result = sGpsMeasurementInterface->init(&sGpsMeasurementCallbacks);
+ if (result != GPS_GEOFENCE_OPERATION_SUCCESS) {
+ ALOGE("An error has been found on GpsMeasurementInterface::init, status=%d", result);
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+static jboolean android_location_GpsLocationProvider_stop_measurement_collection(
+ JNIEnv* env,
+ jobject obj) {
+ if (sGpsMeasurementInterface == NULL) {
+ ALOGE("Measurement interface not available");
+ return JNI_FALSE;
+ }
+
+ sGpsMeasurementInterface->close();
+ return JNI_TRUE;
+}
+
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native},
{"native_is_supported", "()Z", (void*)android_location_GpsLocationProvider_is_supported},
{"native_init", "()Z", (void*)android_location_GpsLocationProvider_init},
{"native_cleanup", "()V", (void*)android_location_GpsLocationProvider_cleanup},
- {"native_set_position_mode", "(IIIII)Z", (void*)android_location_GpsLocationProvider_set_position_mode},
+ {"native_set_position_mode",
+ "(IIIII)Z",
+ (void*)android_location_GpsLocationProvider_set_position_mode},
{"native_start", "()Z", (void*)android_location_GpsLocationProvider_start},
{"native_stop", "()Z", (void*)android_location_GpsLocationProvider_stop},
- {"native_delete_aiding_data", "(I)V", (void*)android_location_GpsLocationProvider_delete_aiding_data},
- {"native_read_sv_status", "([I[F[F[F[I)I", (void*)android_location_GpsLocationProvider_read_sv_status},
+ {"native_delete_aiding_data",
+ "(I)V",
+ (void*)android_location_GpsLocationProvider_delete_aiding_data},
+ {"native_read_sv_status",
+ "([I[F[F[F[I)I",
+ (void*)android_location_GpsLocationProvider_read_sv_status},
{"native_read_nmea", "([BI)I", (void*)android_location_GpsLocationProvider_read_nmea},
{"native_inject_time", "(JJI)V", (void*)android_location_GpsLocationProvider_inject_time},
- {"native_inject_location", "(DDF)V", (void*)android_location_GpsLocationProvider_inject_location},
+ {"native_inject_location",
+ "(DDF)V",
+ (void*)android_location_GpsLocationProvider_inject_location},
{"native_supports_xtra", "()Z", (void*)android_location_GpsLocationProvider_supports_xtra},
- {"native_inject_xtra_data", "([BI)V", (void*)android_location_GpsLocationProvider_inject_xtra_data},
- {"native_agps_data_conn_open", "(Ljava/lang/String;I)V", (void*)android_location_GpsLocationProvider_agps_data_conn_open},
- {"native_agps_data_conn_closed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_closed},
- {"native_agps_data_conn_failed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_failed},
- {"native_agps_set_id","(ILjava/lang/String;)V",(void*)android_location_GpsLocationProvider_agps_set_id},
- {"native_agps_set_ref_location_cellid","(IIIII)V",(void*)android_location_GpsLocationProvider_agps_set_reference_location_cellid},
- {"native_set_agps_server", "(ILjava/lang/String;I)V", (void*)android_location_GpsLocationProvider_set_agps_server},
- {"native_send_ni_response", "(II)V", (void*)android_location_GpsLocationProvider_send_ni_response},
- {"native_agps_ni_message", "([BI)V", (void *)android_location_GpsLocationProvider_agps_send_ni_message},
- {"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state},
- {"native_update_network_state", "(ZIZZLjava/lang/String;Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_update_network_state },
- {"native_is_geofence_supported", "()Z", (void*) android_location_GpsLocationProvider_is_geofence_supported},
- {"native_add_geofence", "(IDDDIIII)Z", (void *)android_location_GpsLocationProvider_add_geofence},
- {"native_remove_geofence", "(I)Z", (void *)android_location_GpsLocationProvider_remove_geofence},
+ {"native_inject_xtra_data",
+ "([BI)V",
+ (void*)android_location_GpsLocationProvider_inject_xtra_data},
+ {"native_agps_data_conn_open",
+ "(Ljava/lang/String;I)V",
+ (void*)android_location_GpsLocationProvider_agps_data_conn_open},
+ {"native_agps_data_conn_closed",
+ "()V",
+ (void*)android_location_GpsLocationProvider_agps_data_conn_closed},
+ {"native_agps_data_conn_failed",
+ "()V",
+ (void*)android_location_GpsLocationProvider_agps_data_conn_failed},
+ {"native_agps_set_id",
+ "(ILjava/lang/String;)V",
+ (void*)android_location_GpsLocationProvider_agps_set_id},
+ {"native_agps_set_ref_location_cellid",
+ "(IIIII)V",
+ (void*)android_location_GpsLocationProvider_agps_set_reference_location_cellid},
+ {"native_set_agps_server",
+ "(ILjava/lang/String;I)V",
+ (void*)android_location_GpsLocationProvider_set_agps_server},
+ {"native_send_ni_response",
+ "(II)V",
+ (void*)android_location_GpsLocationProvider_send_ni_response},
+ {"native_agps_ni_message",
+ "([BI)V",
+ (void *)android_location_GpsLocationProvider_agps_send_ni_message},
+ {"native_get_internal_state",
+ "()Ljava/lang/String;",
+ (void*)android_location_GpsLocationProvider_get_internal_state},
+ {"native_update_network_state",
+ "(ZIZZLjava/lang/String;Ljava/lang/String;)V",
+ (void*)android_location_GpsLocationProvider_update_network_state },
+ {"native_is_geofence_supported",
+ "()Z",
+ (void*) android_location_GpsLocationProvider_is_geofence_supported},
+ {"native_add_geofence",
+ "(IDDDIIII)Z",
+ (void *)android_location_GpsLocationProvider_add_geofence},
+ {"native_remove_geofence",
+ "(I)Z",
+ (void *)android_location_GpsLocationProvider_remove_geofence},
{"native_pause_geofence", "(I)Z", (void *)android_location_GpsLocationProvider_pause_geofence},
- {"native_resume_geofence", "(II)Z", (void *)android_location_GpsLocationProvider_resume_geofence}
+ {"native_resume_geofence",
+ "(II)Z",
+ (void *)android_location_GpsLocationProvider_resume_geofence},
+ {"native_is_measurement_supported",
+ "()Z",
+ (void*) android_location_GpsLocationProvider_is_measurement_supported},
+ {"native_start_measurement_collection",
+ "()Z",
+ (void*) android_location_GpsLocationProvider_start_measurement_collection},
+ {"native_stop_measurement_collection",
+ "()Z",
+ (void*) android_location_GpsLocationProvider_stop_measurement_collection}
};
int register_android_server_location_GpsLocationProvider(JNIEnv* env)
{
- return jniRegisterNativeMethods(env, "com/android/server/location/GpsLocationProvider", sMethods, NELEM(sMethods));
+ return jniRegisterNativeMethods(
+ env,
+ "com/android/server/location/GpsLocationProvider",
+ sMethods,
+ NELEM(sMethods));
}
} /* namespace android */
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e0ebd54..8d38827 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -910,7 +910,9 @@
mSystemServiceManager.startService(MediaSessionService.class);
- mSystemServiceManager.startService(HdmiControlService.class);
+ if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
+ mSystemServiceManager.startService(HdmiControlService.class);
+ }
mSystemServiceManager.startService(TvInputManagerService.class);
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 6822ee3..5233297 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -39,6 +39,7 @@
import android.os.SystemProperties;
import android.os.UEventObserver;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.provider.Settings;
@@ -655,6 +656,17 @@
}
break;
case MSG_USER_SWITCHED: {
+ mCurrentUser = msg.arg1;
+
+ UserManager userManager =
+ (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
+ Slog.v(TAG, "Switched to user with DISALLOW_USB_FILE_TRANSFER restriction;"
+ + " disabling USB.");
+ setUsbConfig("none");
+ break;
+ }
+
final boolean mtpActive =
containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)
|| containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP);
@@ -663,7 +675,6 @@
setUsbConfig("none");
setUsbConfig(mCurrentFunctions);
}
- mCurrentUser = msg.arg1;
break;
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index b6ae192..fd83f92 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -248,6 +249,15 @@
@Override
public void setCurrentFunction(String function, boolean makeDefault) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+
+ // If attempt to change USB function while file transfer is restricted, ensure that
+ // the current function is set to "none", and return.
+ UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
+ if (mDeviceManager != null) mDeviceManager.setCurrentFunctions("none", false);
+ return;
+ }
+
if (mDeviceManager != null) {
mDeviceManager.setCurrentFunctions(function, makeDefault);
} else {
diff --git a/telecomm/java/android/telecomm/CallCapabilities.java b/telecomm/java/android/telecomm/CallCapabilities.java
index 924526e..e64fe80 100644
--- a/telecomm/java/android/telecomm/CallCapabilities.java
+++ b/telecomm/java/android/telecomm/CallCapabilities.java
@@ -48,8 +48,12 @@
/** Remote device supports video telephony. */
public static final int SUPPORTS_VT_REMOTE = 0x00000200;
+ public static final int VoLTE = 0x00000400;
+
+ public static final int VoWIFI = 0x00000800;
+
public static final int ALL = HOLD | SUPPORT_HOLD | MERGE_CALLS | SWAP_CALLS | ADD_CALL
- | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE;
+ | RESPOND_VIA_TEXT | MUTE | GENERIC_CONFERENCE | VoLTE | VoWIFI;
public static String toString(int capabilities) {
StringBuilder builder = new StringBuilder();
@@ -84,6 +88,12 @@
if ((capabilities & SUPPORTS_VT_REMOTE) != 0) {
builder.append(" SUPPORTS_VT_REMOTE");
}
+ if ((capabilities & VoLTE) != 0) {
+ builder.append(" VoLTE");
+ }
+ if ((capabilities & VoWIFI) != 0) {
+ builder.append(" VoWIFI");
+ }
builder.append("]");
return builder.toString();
}
diff --git a/telecomm/java/android/telecomm/CallFeatures.java b/telecomm/java/android/telecomm/CallFeatures.java
deleted file mode 100644
index 076d25a..0000000
--- a/telecomm/java/android/telecomm/CallFeatures.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.telecomm;
-
-/**
- * Defines features of a call. These features represent properties of a call for which it may be
- * desirable to display an in-call indicator.
- */
-public final class CallFeatures {
- private CallFeatures() {};
-
- public static final int NONE = 0x0;
- public static final int VoLTE = 0x1;
- public static final int VoWIFI = 0x2;
-}
\ No newline at end of file
diff --git a/telecomm/java/android/telecomm/CallNumberPresentation.java b/telecomm/java/android/telecomm/CallPropertyPresentation.java
similarity index 63%
rename from telecomm/java/android/telecomm/CallNumberPresentation.java
rename to telecomm/java/android/telecomm/CallPropertyPresentation.java
index 6cd22f8..350980c 100644
--- a/telecomm/java/android/telecomm/CallNumberPresentation.java
+++ b/telecomm/java/android/telecomm/CallPropertyPresentation.java
@@ -16,17 +16,17 @@
package android.telecomm;
-/** Defines how numbers are displayed in caller id. */
-public enum CallNumberPresentation {
- /** Number is displayed normally. */
- ALLOWED,
+/** Defines how numbers and names are displayed in caller id. */
+public class CallPropertyPresentation {
+ /** Property is displayed normally. */
+ public static final int ALLOWED = 0;
- /** Number was blocked. */
- RESTRICTED,
+ /** Property was blocked. */
+ public static final int RESTRICTED = 1;
/** Presentation was not specified or is unknown. */
- UNKNOWN,
+ public static final int UNKNOWN = 2;
- /** Number should be displayed as a pay phone. */
- PAYPHONE
+ /** Property should be displayed as a pay phone. */
+ public static final int PAYPHONE = 3;
}
diff --git a/telecomm/java/android/telecomm/Connection.java b/telecomm/java/android/telecomm/Connection.java
index 05b0062..fb820f0 100644
--- a/telecomm/java/android/telecomm/Connection.java
+++ b/telecomm/java/android/telecomm/Connection.java
@@ -32,62 +32,20 @@
public abstract class Connection {
/** @hide */
- public interface Listener {
- void onStateChanged(Connection c, int state);
- void onFeaturesChanged(Connection c, int features);
- void onHandleChanged(Connection c, Uri newHandle);
- void onSignalChanged(Connection c, Bundle details);
- void onDisconnected(Connection c, int cause, String message);
- void onPostDialWait(Connection c, String remaining);
- void onRequestingRingback(Connection c, boolean ringback);
- void onDestroyed(Connection c);
- void onCallCapabilitiesChanged(Connection c, int callCapabilities);
- void onParentConnectionChanged(Connection c, Connection parent);
- void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider);
- void onSetAudioModeIsVoip(Connection c, boolean isVoip);
- void onSetStatusHints(Connection c, StatusHints statusHints);
- }
-
- /** @hide */
- public static class ListenerBase implements Listener {
- @Override
+ public abstract static class Listener {
public void onStateChanged(Connection c, int state) {}
-
- /** {@inheritDoc} */
- @Override
- public void onFeaturesChanged(Connection c, int features) {}
-
- @Override
- public void onHandleChanged(Connection c, Uri newHandle) {}
-
- @Override
+ public void onHandleChanged(Connection c, Uri newHandle, int presentation) {}
+ public void onCallerDisplayNameChanged(
+ Connection c, String callerDisplayName, int presentation) {}
public void onSignalChanged(Connection c, Bundle details) {}
-
- @Override
public void onDisconnected(Connection c, int cause, String message) {}
-
- @Override
- public void onDestroyed(Connection c) {}
-
- @Override
public void onPostDialWait(Connection c, String remaining) {}
-
- @Override
public void onRequestingRingback(Connection c, boolean ringback) {}
-
- @Override
+ public void onDestroyed(Connection c) {}
public void onCallCapabilitiesChanged(Connection c, int callCapabilities) {}
-
- @Override
public void onParentConnectionChanged(Connection c, Connection parent) {}
-
- @Override
public void onSetCallVideoProvider(Connection c, CallVideoProvider callVideoProvider) {}
-
- @Override
public void onSetAudioModeIsVoip(Connection c, boolean isVoip) {}
-
- @Override
public void onSetStatusHints(Connection c, StatusHints statusHints) {}
}
@@ -106,9 +64,11 @@
private final List<Connection> mChildConnections = new ArrayList<>();
private int mState = State.NEW;
- private int mFeatures = CallFeatures.NONE;
private CallAudioState mCallAudioState;
private Uri mHandle;
+ private int mHandlePresentation;
+ private String mCallerDisplayName;
+ private int mCallerDisplayNamePresentation;
private boolean mRequestingRingback = false;
private int mCallCapabilities;
private Connection mParentConnection;
@@ -122,21 +82,35 @@
protected Connection() {}
/**
- * The handle (e.g., phone number) to which this Connection is currently communicating.
- *
- * IMPORTANT: If an incoming connection has a phone number (or other handle) that the user
- * is not supposed to be able to see (e.g. it is PRESENTATION_RESTRICTED), then a compliant
- * ConnectionService implementation MUST NOT reveal this phone number and MUST return
- * {@code null} from this method.
- *
- * @return The handle (e.g., phone number) to which this Connection
- * is currently communicating.
+ * @return The handle (e.g., phone number) to which this Connection is currently communicating.
*/
public final Uri getHandle() {
return mHandle;
}
/**
+ * @return The {@link CallPropertyPresentation} which controls how the handle is shown.
+ */
+ public final int getHandlePresentation() {
+ return mHandlePresentation;
+ }
+
+ /**
+ * @return The caller display name (CNAP).
+ */
+ public final String getCallerDisplayName() {
+ return mCallerDisplayName;
+ }
+
+ /**
+ * @return The {@link CallPropertyPresentation} which controls how the caller display name is
+ * shown.
+ */
+ public final int getCallerDisplayNamePresentation() {
+ return mCallerDisplayNamePresentation;
+ }
+
+ /**
* @return The state of this Connection.
*/
public final int getState() {
@@ -144,14 +118,6 @@
}
/**
- * @return The features of the call. These are items for which the InCall UI may wish to
- * display a visual indicator.
- */
- public final int getFeatures() {
- return mFeatures;
- }
-
- /**
* @return The audio state of the call, describing how its audio is currently
* being routed by the system. This is {@code null} if this Connection
* does not directly know about its audio state.
@@ -287,31 +253,34 @@
}
/**
- * Sets the value of the {@link #getHandle()} property and notifies listeners.
+ * Sets the value of the {@link #getHandle()} property.
*
* @param handle The new handle.
+ * @param presentation The {@link CallPropertyPresentation} which controls how the handle is
+ * shown.
*/
- public final void setHandle(Uri handle) {
+ public final void setHandle(Uri handle, int presentation) {
Log.d(this, "setHandle %s", handle);
mHandle = handle;
+ mHandlePresentation = presentation;
for (Listener l : mListeners) {
- l.onHandleChanged(this, handle);
+ l.onHandleChanged(this, handle, presentation);
}
}
/**
- * Set the features applicable to the connection. These are items for which the InCall UI may
- * wish to display a visual indicator.
- * Features are defined in {@link android.telecomm.CallFeatures} and are passed in as a
- * bit-mask.
+ * Sets the caller display name (CNAP).
*
- * @param features The features active.
+ * @param callerDisplayName The new display name.
+ * @param presentation The {@link CallPropertyPresentation} which controls how the name is
+ * shown.
*/
- public final void setFeatures(int features) {
- Log.d(this, "setFeatures %d", features);
- mFeatures = features;
+ public final void setCallerDisplayName(String callerDisplayName, int presentation) {
+ Log.d(this, "setCallerDisplayName %s", callerDisplayName);
+ mCallerDisplayName = callerDisplayName;
+ mCallerDisplayNamePresentation = presentation;
for (Listener l : mListeners) {
- l.onFeaturesChanged(this, mFeatures);
+ l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
}
}
@@ -428,8 +397,7 @@
}
/**
- * Notifies this Connection and listeners of a change in the current signal levels
- * for the underlying data transport.
+ * Sets the current signal levels for the underlying data transport.
*
* @param details A {@link android.os.Bundle} containing details of the current level.
*/
@@ -464,8 +432,7 @@
}
/**
- * Notifies this Connection and listeners that the {@link #getCallAudioState()} property
- * has a new value.
+ * Notifies this Connection that the {@link #getCallAudioState()} property has a new value.
*
* @param state The new call audio state.
*/
@@ -534,6 +501,12 @@
protected void onPostDialContinue(boolean proceed) {}
/**
+ * Swap this call with a background call. This is used for calls that don't support hold,
+ * e.g. CDMA.
+ */
+ protected void onSwapWithBackgroundCall() {}
+
+ /**
* TODO(santoscordon): Needs documentation.
*/
protected void onChildrenChanged(List<Connection> children) {}
diff --git a/telecomm/java/android/telecomm/ConnectionRequest.java b/telecomm/java/android/telecomm/ConnectionRequest.java
index 28fea79..0db9e29 100644
--- a/telecomm/java/android/telecomm/ConnectionRequest.java
+++ b/telecomm/java/android/telecomm/ConnectionRequest.java
@@ -16,8 +16,8 @@
package android.telecomm;
-import android.os.Bundle;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,20 +32,32 @@
// numbers that would satisfy the client's needs, in order of preference
private final String mCallId;
private final Uri mHandle;
+ private final int mHandlePresentation;
private final Bundle mExtras;
private final PhoneAccount mAccount;
private final int mVideoState;
- public ConnectionRequest(String callId, Uri handle, Bundle extras, int videoState) {
- this(null, callId, handle, extras, videoState);
- }
-
- public ConnectionRequest(PhoneAccount account, String callId, Uri handle, Bundle extras,
+ /**
+ * @param account The account which should be used to place the call.
+ * @param callId An identifier for this call.
+ * @param handle The handle (e.g., phone number) to which the {@link Connection} is to connect.
+ * @param handlePresentation The {@link CallPropertyPresentation} which controls how the handle
+ * is shown.
+ * @param extras Application-specific extra data.
+ * @param videoState Determines the video state for the connection.
+ */
+ public ConnectionRequest(
+ PhoneAccount account,
+ String callId,
+ Uri handle,
+ int handlePresentation,
+ Bundle extras,
int videoState) {
+ mAccount = account;
mCallId = callId;
mHandle = handle;
+ mHandlePresentation = handlePresentation;
mExtras = extras;
- mAccount = account;
mVideoState = videoState;
}
@@ -65,6 +77,11 @@
public Uri getHandle() { return mHandle; }
/**
+ * The {@link CallPropertyPresentation} which controls how the handle is shown.
+ */
+ public int getHandlePresentation() { return mHandlePresentation; }
+
+ /**
* Application-specific extra data. Used for passing back information from an incoming
* call {@code Intent}, and for any proprietary extensions arranged between a client
* and servant {@code ConnectionService} which agree on a vocabulary for such data.
@@ -96,11 +113,15 @@
new Parcelable.Creator<ConnectionRequest> () {
@Override
public ConnectionRequest createFromParcel(Parcel source) {
+ PhoneAccount account = (PhoneAccount) source.readParcelable(
+ getClass().getClassLoader());
String callId = source.readString();
Uri handle = (Uri) source.readParcelable(getClass().getClassLoader());
+ int presentation = source.readInt();
Bundle extras = (Bundle) source.readParcelable(getClass().getClassLoader());
int videoState = source.readInt();
- return new ConnectionRequest(callId, handle, extras, videoState);
+ return new ConnectionRequest(
+ account, callId, handle, presentation, extras, videoState);
}
@Override
@@ -119,8 +140,10 @@
@Override
public void writeToParcel(Parcel destination, int flags) {
+ destination.writeParcelable(mAccount, 0);
destination.writeString(mCallId);
destination.writeParcelable(mHandle, 0);
+ destination.writeInt(mHandlePresentation);
destination.writeParcelable(mExtras, 0);
destination.writeInt(mVideoState);
}
diff --git a/telecomm/java/android/telecomm/ConnectionService.java b/telecomm/java/android/telecomm/ConnectionService.java
index d0ad0cc..1966081 100644
--- a/telecomm/java/android/telecomm/ConnectionService.java
+++ b/telecomm/java/android/telecomm/ConnectionService.java
@@ -60,8 +60,9 @@
private static final int MSG_STOP_DTMF_TONE = 12;
private static final int MSG_CONFERENCE = 13;
private static final int MSG_SPLIT_FROM_CONFERENCE = 14;
- private static final int MSG_ON_POST_DIAL_CONTINUE = 15;
- private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 16;
+ private static final int MSG_SWAP_WITH_BACKGROUND_CALL = 15;
+ private static final int MSG_ON_POST_DIAL_CONTINUE = 16;
+ private static final int MSG_ON_PHONE_ACCOUNT_CLICKED = 17;
private final Map<String, Connection> mConnectionById = new HashMap<>();
private final Map<Connection, String> mIdByConnection = new HashMap<>();
@@ -179,6 +180,11 @@
}
@Override
+ public void swapWithBackgroundCall(String callId) {
+ mHandler.obtainMessage(MSG_SWAP_WITH_BACKGROUND_CALL, callId).sendToTarget();
+ }
+
+ @Override
public void onPostDialContinue(String callId, boolean proceed) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = callId;
@@ -255,6 +261,9 @@
case MSG_SPLIT_FROM_CONFERENCE:
splitFromConference((String) msg.obj);
break;
+ case MSG_SWAP_WITH_BACKGROUND_CALL:
+ swapWithBackgroundCall((String) msg.obj);
+ break;
case MSG_ON_POST_DIAL_CONTINUE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
@@ -302,14 +311,6 @@
}
}
- /** {@inheritDoc} */
- @Override
- public void onFeaturesChanged(Connection c, int features) {
- String id = mIdByConnection.get(c);
- Log.d(this, "Adapter set features %d", features);
- mAdapter.setFeatures(id, features);
- }
-
@Override
public void onDisconnected(Connection c, int cause, String message) {
String id = mIdByConnection.get(c);
@@ -318,8 +319,16 @@
}
@Override
- public void onHandleChanged(Connection c, Uri newHandle) {
- // TODO: Unsupported yet
+ public void onHandleChanged(Connection c, Uri handle, int presentation) {
+ String id = mIdByConnection.get(c);
+ mAdapter.setHandle(id, handle, presentation);
+ }
+
+ @Override
+ public void onCallerDisplayNameChanged(
+ Connection c, String callerDisplayName, int presentation) {
+ String id = mIdByConnection.get(c);
+ mAdapter.setCallerDisplayName(id, callerDisplayName, presentation);
}
@Override
@@ -527,6 +536,11 @@
// TODO(santoscordon): Find existing conference call and invoke split(connection).
}
+ private void swapWithBackgroundCall(String callId) {
+ Log.d(this, "swapWithBackgroundCall(%s)", callId);
+ findConnectionForAction(callId, "swapWithBackgroundCall").onSwapWithBackgroundCall();
+ }
+
private void onPostDialContinue(String callId, boolean proceed) {
Log.d(this, "onPostDialContinue(%s)", callId);
findConnectionForAction(callId, "stopDtmfTone").onPostDialContinue(proceed);
diff --git a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
index b60e7c6..b90dec3 100644
--- a/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecomm/ConnectionServiceAdapter.java
@@ -17,6 +17,7 @@
package android.telecomm;
import android.content.ComponentName;
+import android.net.Uri;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
@@ -28,6 +29,7 @@
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -61,9 +63,12 @@
/** ${inheritDoc} */
@Override
public void binderDied() {
- for (IConnectionServiceAdapter adapter : mAdapters) {
+ Iterator<IConnectionServiceAdapter> it = mAdapters.iterator();
+ while (it.hasNext()) {
+ IConnectionServiceAdapter adapter = it.next();
if (!adapter.asBinder().isBinderAlive()) {
- removeAdapter(adapter);
+ it.remove();
+ adapter.asBinder().unlinkToDeath(this, 0);
}
}
}
@@ -338,20 +343,20 @@
}
}
- /**
- * Set the features associated with the given call.
- * Features are defined in {@link android.telecomm.CallFeatures} and are passed in as a
- * bit-mask.
- *
- * @param callId The unique ID of the call to set features for.
- * @param features The features.
- */
- void setFeatures(String callId, int features) {
- Log.v(this, "setFeatures: %d", features);
+ void setHandle(String callId, Uri handle, int presentation) {
for (IConnectionServiceAdapter adapter : mAdapters) {
try {
- adapter.setFeatures(callId, features);
- } catch (RemoteException ignored) {
+ adapter.setHandle(callId, handle, presentation);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ void setCallerDisplayName(String callId, String callerDisplayName, int presentation) {
+ for (IConnectionServiceAdapter adapter : mAdapters) {
+ try {
+ adapter.setCallerDisplayName(callId, callerDisplayName, presentation);
+ } catch (RemoteException e) {
}
}
}
diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java
index aef05fd..d8293a5 100644
--- a/telecomm/java/android/telecomm/InCallAdapter.java
+++ b/telecomm/java/android/telecomm/InCallAdapter.java
@@ -226,4 +226,17 @@
} catch (RemoteException ignored) {
}
}
+
+ /**
+ * Swap this call with a background call. This is used for calls that don't support hold,
+ * e.g. CDMA.
+ *
+ * @param callId The unique ID of the call.
+ */
+ public void swapWithBackgroundCall(String callId) {
+ try {
+ mAdapter.swapWithBackgroundCall(callId);
+ } catch (RemoteException ignored) {
+ }
+ }
}
diff --git a/telecomm/java/android/telecomm/InCallCall.java b/telecomm/java/android/telecomm/InCallCall.java
index 5396688..7c35020 100644
--- a/telecomm/java/android/telecomm/InCallCall.java
+++ b/telecomm/java/android/telecomm/InCallCall.java
@@ -40,6 +40,9 @@
private final int mCapabilities;
private final long mConnectTimeMillis;
private final Uri mHandle;
+ private final int mHandlePresentation;
+ private final String mCallerDisplayName;
+ private final int mCallerDisplayNamePresentation;
private final GatewayInfo mGatewayInfo;
private final PhoneAccount mAccount;
private final CallServiceDescriptor mCurrentCallServiceDescriptor;
@@ -47,7 +50,6 @@
private RemoteCallVideoProvider mRemoteCallVideoProvider;
private final String mParentCallId;
private final List<String> mChildCallIds;
- private final int mFeatures;
private final StatusHints mStatusHints;
/** @hide */
@@ -60,13 +62,15 @@
int capabilities,
long connectTimeMillis,
Uri handle,
+ int handlePresentation,
+ String callerDisplayName,
+ int callerDisplayNamePresentation,
GatewayInfo gatewayInfo,
PhoneAccount account,
CallServiceDescriptor descriptor,
ICallVideoProvider callVideoProvider,
String parentCallId,
List<String> childCallIds,
- int features,
StatusHints statusHints) {
mId = id;
mState = state;
@@ -76,13 +80,15 @@
mCapabilities = capabilities;
mConnectTimeMillis = connectTimeMillis;
mHandle = handle;
+ mHandlePresentation = handlePresentation;
+ mCallerDisplayName = callerDisplayName;
+ mCallerDisplayNamePresentation = callerDisplayNamePresentation;
mGatewayInfo = gatewayInfo;
mAccount = account;
mCurrentCallServiceDescriptor = descriptor;
mCallVideoProvider = callVideoProvider;
mParentCallId = parentCallId;
mChildCallIds = childCallIds;
- mFeatures = features;
mStatusHints = statusHints;
}
@@ -134,6 +140,21 @@
return mHandle;
}
+ /** The {@link CallPropertyPresentation} which controls how the handle is shown. */
+ public int getHandlePresentation() {
+ return mHandlePresentation;
+ }
+
+ /** The endpoint to which the call is connected. */
+ public String getCallerDisplayName() {
+ return mCallerDisplayName;
+ }
+
+ /** The {@link CallPropertyPresentation} which controls how the caller display name is shown. */
+ public int getCallerDisplayNamePresentation() {
+ return mCallerDisplayNamePresentation;
+ }
+
/** Gateway information for the call. */
public GatewayInfo getGatewayInfo() {
return mGatewayInfo;
@@ -183,15 +204,6 @@
}
/**
- * The features of this call (e.g. VoLTE, VoWIFI).
- *
- * @return Features.
- */
- public int getFeatures() {
- return mFeatures;
- }
-
- /**
* The status label and icon.
*
* @return Status hints.
@@ -215,6 +227,9 @@
int capabilities = source.readInt();
long connectTimeMillis = source.readLong();
Uri handle = source.readParcelable(classLoader);
+ int handlePresentation = source.readInt();
+ String callerDisplayName = source.readString();
+ int callerDisplayNamePresentation = source.readInt();
GatewayInfo gatewayInfo = source.readParcelable(classLoader);
PhoneAccount account = source.readParcelable(classLoader);
CallServiceDescriptor descriptor = source.readParcelable(classLoader);
@@ -223,11 +238,11 @@
String parentCallId = source.readString();
List<String> childCallIds = new ArrayList<>();
source.readList(childCallIds, classLoader);
- int features = source.readInt();
StatusHints statusHints = source.readParcelable(classLoader);
return new InCallCall(id, state, disconnectCauseCode, disconnectCauseMsg,
- cannedSmsResponses, capabilities, connectTimeMillis, handle, gatewayInfo,
- account, descriptor, callVideoProvider, parentCallId, childCallIds, features,
+ cannedSmsResponses, capabilities, connectTimeMillis, handle, handlePresentation,
+ callerDisplayName, callerDisplayNamePresentation, gatewayInfo,
+ account, descriptor, callVideoProvider, parentCallId, childCallIds,
statusHints);
}
@@ -254,6 +269,9 @@
destination.writeInt(mCapabilities);
destination.writeLong(mConnectTimeMillis);
destination.writeParcelable(mHandle, 0);
+ destination.writeInt(mHandlePresentation);
+ destination.writeString(mCallerDisplayName);
+ destination.writeInt(mCallerDisplayNamePresentation);
destination.writeParcelable(mGatewayInfo, 0);
destination.writeParcelable(mAccount, 0);
destination.writeParcelable(mCurrentCallServiceDescriptor, 0);
@@ -261,7 +279,6 @@
mCallVideoProvider != null ? mCallVideoProvider.asBinder() : null);
destination.writeString(mParentCallId);
destination.writeList(mChildCallIds);
- destination.writeInt(mFeatures);
destination.writeParcelable(mStatusHints, 0);
}
diff --git a/telecomm/java/android/telecomm/RemoteConnection.java b/telecomm/java/android/telecomm/RemoteConnection.java
index 8ef283a..3475305 100644
--- a/telecomm/java/android/telecomm/RemoteConnection.java
+++ b/telecomm/java/android/telecomm/RemoteConnection.java
@@ -37,9 +37,11 @@
void onRequestingRingback(RemoteConnection connection, boolean ringback);
void onCallCapabilitiesChanged(RemoteConnection connection, int callCapabilities);
void onPostDialWait(RemoteConnection connection, String remainingDigits);
- void onFeaturesChanged(RemoteConnection connection, int features);
- void onSetAudioModeIsVoip(RemoteConnection connection, boolean isVoip);
- void onSetStatusHints(RemoteConnection connection, StatusHints statusHints);
+ void onAudioModeIsVoipChanged(RemoteConnection connection, boolean isVoip);
+ void onStatusHintsChanged(RemoteConnection connection, StatusHints statusHints);
+ void onHandleChanged(RemoteConnection connection, Uri handle, int presentation);
+ void onCallerDisplayNameChanged(
+ RemoteConnection connection, String callerDisplayName, int presentation);
void onDestroyed(RemoteConnection connection);
}
@@ -53,9 +55,12 @@
private boolean mRequestingRingback;
private boolean mConnected;
private int mCallCapabilities;
- private int mFeatures;
private boolean mAudioModeIsVoip;
private StatusHints mStatusHints;
+ private Uri mHandle;
+ private int mHandlePresentation;
+ private String mCallerDisplayName;
+ private int mCallerDisplayNamePresentation;
/**
* @hide
@@ -91,10 +96,6 @@
return mCallCapabilities;
}
- public int getFeatures() {
- return mFeatures;
- }
-
public boolean getAudioModeIsVoip() {
return mAudioModeIsVoip;
}
@@ -103,6 +104,22 @@
return mStatusHints;
}
+ public Uri getHandle() {
+ return mHandle;
+ }
+
+ public int getHandlePresentation() {
+ return mHandlePresentation;
+ }
+
+ public String getCallerDisplayName() {
+ return mCallerDisplayName;
+ }
+
+ public int getCallerDisplayNamePresentation() {
+ return mCallerDisplayNamePresentation;
+ }
+
public void abort() {
try {
if (mConnected) {
@@ -184,6 +201,15 @@
}
}
+ public void swapWithBackgroundCall() {
+ try {
+ if (mConnected) {
+ mConnectionService.swapWithBackgroundCall(mConnectionId);
+ }
+ } catch (RemoteException ignored) {
+ }
+ }
+
public void setAudioState(CallAudioState state) {
try {
if (mConnected) {
@@ -271,21 +297,11 @@
}
}
- /**
- * @hide
- */
- void setFeatures(int features) {
- mFeatures = features;
- for (Listener l : mListeners) {
- l.onFeaturesChanged(this, features);
- }
- }
-
/** @hide */
void setAudioModeIsVoip(boolean isVoip) {
mAudioModeIsVoip = isVoip;
for (Listener l : mListeners) {
- l.onSetAudioModeIsVoip(this, isVoip);
+ l.onAudioModeIsVoipChanged(this, isVoip);
}
}
@@ -293,7 +309,25 @@
void setStatusHints(StatusHints statusHints) {
mStatusHints = statusHints;
for (Listener l : mListeners) {
- l.onSetStatusHints(this, statusHints);
+ l.onStatusHintsChanged(this, statusHints);
+ }
+ }
+
+ /** @hide */
+ void setHandle(Uri handle, int presentation) {
+ mHandle = handle;
+ mHandlePresentation = presentation;
+ for (Listener l : mListeners) {
+ l.onHandleChanged(this, handle, presentation);
+ }
+ }
+
+ /** @hide */
+ void setCallerDisplayName(String callerDisplayName, int presentation) {
+ mCallerDisplayName = callerDisplayName;
+ mCallerDisplayNamePresentation = presentation;
+ for (Listener l : mListeners) {
+ l.onCallerDisplayNameChanged(this, callerDisplayName, presentation);
}
}
}
diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java
index c22005b..a436af2 100644
--- a/telecomm/java/android/telecomm/RemoteConnectionService.java
+++ b/telecomm/java/android/telecomm/RemoteConnectionService.java
@@ -50,13 +50,11 @@
private final IConnectionServiceAdapter mAdapter = new IConnectionServiceAdapter.Stub() {
- /** ${inheritDoc} */
@Override
public void notifyIncomingCall(ConnectionRequest request) {
Log.w(this, "notifyIncomingCall not implemented in Remote connection");
}
- /** ${inheritDoc} */
@Override
public void handleSuccessfulOutgoingCall(ConnectionRequest request) {
if (isPendingConnection(request.getCallId())) {
@@ -66,7 +64,6 @@
}
}
- /** ${inheritDoc} */
@Override
public void handleFailedOutgoingCall(
ConnectionRequest request, int errorCode, String errorMessage) {
@@ -77,7 +74,6 @@
}
}
- /** ${inheritDoc} */
@Override
public void cancelOutgoingCall(ConnectionRequest request) {
if (isPendingConnection(request.getCallId())) {
@@ -87,7 +83,6 @@
}
}
- /** ${inheritDoc} */
@Override
public void setActive(String connectionId) {
if (isCurrentConnection(connectionId)) {
@@ -95,7 +90,6 @@
}
}
- /** ${inheritDoc} */
@Override
public void setRinging(String connectionId) {
if (isCurrentConnection(connectionId)) {
@@ -103,13 +97,12 @@
}
}
- /** ${inheritDoc} */
+ @Override
public void setCallVideoProvider(
String connectionId, ICallVideoProvider callVideoProvider) {
// not supported for remote connections.
}
- /** ${inheritDoc} */
@Override
public void setDialing(String connectionId) {
if (isCurrentConnection(connectionId)) {
@@ -117,7 +110,6 @@
}
}
- /** ${inheritDoc} */
@Override
public void setDisconnected(
String connectionId, int disconnectCause, String disconnectMessage) {
@@ -126,7 +118,6 @@
}
}
- /** ${inheritDoc} */
@Override
public void setOnHold(String connectionId) {
if (isCurrentConnection(connectionId)) {
@@ -134,7 +125,6 @@
}
}
- /** ${inheritDoc} */
@Override
public void setRequestingRingback(String connectionId, boolean isRequestingRingback) {
if (isCurrentConnection(connectionId)) {
@@ -142,7 +132,6 @@
}
}
- /** ${inheritDoc} */
@Override
public void setCallCapabilities(String connectionId, int callCapabilities) {
if (isCurrentConnection(connectionId)) {
@@ -150,19 +139,16 @@
}
}
- /** ${inheritDoc} */
@Override
public void setIsConferenced(String connectionId, String conferenceConnectionId) {
// not supported for remote connections.
}
- /** ${inheritDoc} */
@Override
public void addConferenceCall(String connectionId) {
// not supported for remote connections.
}
- /** ${inheritDoc} */
@Override
public void removeCall(String connectionId) {
if (isCurrentConnection(connectionId)) {
@@ -170,7 +156,6 @@
}
}
- /** ${inheritDoc} */
@Override
public void onPostDialWait(String connectionId, String remainingDigits) {
if (isCurrentConnection(connectionId)) {
@@ -178,7 +163,6 @@
}
}
- /** ${inheritDoc} */
@Override
public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
try {
@@ -188,15 +172,6 @@
}
}
- /** ${inheritDoc} */
- @Override
- public void setFeatures(String connectionId, int features) {
- if (isCurrentConnection(connectionId)) {
- mConnection.setFeatures(features);
- }
- }
-
- /** ${inheritDoc} */
@Override
public final void setAudioModeIsVoip(String connectionId, boolean isVoip) {
if (isCurrentConnection(connectionId)) {
@@ -204,13 +179,27 @@
}
}
- /** ${inheritDoc} */
@Override
public final void setStatusHints(String connectionId, StatusHints statusHints) {
if (isCurrentConnection(connectionId)) {
mConnection.setStatusHints(statusHints);
}
}
+
+ @Override
+ public final void setHandle(String connectionId, Uri handle, int presentation) {
+ if (isCurrentConnection(connectionId)) {
+ mConnection.setHandle(handle, presentation);
+ }
+ }
+
+ @Override
+ public final void setCallerDisplayName(
+ String connectionId, String callerDisplayName, int presentation) {
+ if (isCurrentConnection(connectionId)) {
+ mConnection.setCallerDisplayName(callerDisplayName, presentation);
+ }
+ }
};
RemoteConnectionService(ComponentName componentName, IConnectionService connectionService)
@@ -246,8 +235,13 @@
if (mConnectionId == null) {
String id = UUID.randomUUID().toString();
- ConnectionRequest newRequest = new ConnectionRequest(request.getAccount(), id,
- request.getHandle(), request.getExtras(), request.getVideoState());
+ ConnectionRequest newRequest = new ConnectionRequest(
+ request.getAccount(),
+ id,
+ request.getHandle(),
+ request.getHandlePresentation(),
+ request.getExtras(),
+ request.getVideoState());
try {
mConnectionService.call(newRequest);
mConnectionId = id;
diff --git a/telecomm/java/android/telecomm/StatusHints.java b/telecomm/java/android/telecomm/StatusHints.java
index 354dc36..5e64bff 100644
--- a/telecomm/java/android/telecomm/StatusHints.java
+++ b/telecomm/java/android/telecomm/StatusHints.java
@@ -21,6 +21,7 @@
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.DisplayMetrics;
@@ -35,11 +36,13 @@
private final ComponentName mComponentName;
private final String mLabel;
private final int mIconId;
+ private final Bundle mExtras;
- public StatusHints(ComponentName componentName, String label, int iconId) {
+ public StatusHints(ComponentName componentName, String label, int iconId, Bundle extras) {
mComponentName = componentName;
mLabel = label;
mIconId = iconId;
+ mExtras = extras;
}
/**
@@ -70,6 +73,13 @@
return getIcon(context, mIconId);
}
+ /**
+ * @return Extra data used to display status.
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -80,6 +90,7 @@
out.writeParcelable(mComponentName, flags);
out.writeString(mLabel);
out.writeInt(mIconId);
+ out.writeParcelable(mExtras, 0);
}
public static final Creator<StatusHints> CREATOR
@@ -97,6 +108,7 @@
mComponentName = in.readParcelable(getClass().getClassLoader());
mLabel = in.readString();
mIconId = in.readInt();
+ mExtras = (Bundle) in.readParcelable(getClass().getClassLoader());
}
private Drawable getIcon(Context context, int resId) {
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
index c8ce4c6..16d2edf 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionService.aidl
@@ -58,6 +58,8 @@
void splitFromConference(String callId);
+ void swapWithBackgroundCall(String callId);
+
void onPostDialContinue(String callId, boolean proceed);
void onPhoneAccountClicked(String callId);
diff --git a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
index e724bfb..bc67eab 100644
--- a/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IConnectionServiceAdapter.aidl
@@ -16,6 +16,7 @@
package com.android.internal.telecomm;
+import android.net.Uri;
import android.telecomm.ConnectionRequest;
import android.telecomm.StatusHints;
@@ -64,9 +65,11 @@
void setCallVideoProvider(String callId, ICallVideoProvider callVideoProvider);
- void setFeatures(String callId, int features);
-
void setAudioModeIsVoip(String callId, boolean isVoip);
void setStatusHints(String callId, in StatusHints statusHints);
+
+ void setHandle(String callId, in Uri handle, int presentation);
+
+ void setCallerDisplayName(String callId, String callerDisplayName, int presentation);
}
diff --git a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
index 7f3767c..ce0309f 100644
--- a/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecomm/IInCallAdapter.aidl
@@ -51,4 +51,6 @@
void conference(String callId);
void splitFromConference(String callId);
+
+ void swapWithBackgroundCall(String callId);
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 15320e6..1c84741 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1695,6 +1695,15 @@
*/
/** {@hide} */
public String getLine1Number(long subId) {
+ String number = null;
+ try {
+ number = getITelephony().getLine1NumberForDisplay(subId);
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ if (number != null) {
+ return number;
+ }
try {
return getSubscriberInfo().getLine1NumberUsingSubId(subId);
} catch (RemoteException ex) {
@@ -1706,6 +1715,47 @@
}
/**
+ * Set the phone number string and its alphatag for line 1 for display
+ * purpose only, for example, displayed in Phone Status. It won't change
+ * the actual MSISDN/MDN. This setting won't be persisted during power cycle
+ * and it should be set again after reboot.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ *
+ * @param alphaTag alpha-tagging of the dailing nubmer
+ * @param number The dialing number
+ *
+ * @hide
+ */
+ public void setLine1NumberForDisplay(String alphaTag, String number) {
+ setLine1NumberForDisplay(getDefaultSubscription(), alphaTag, number);
+ }
+
+ /**
+ * Set the phone number string and its alphatag for line 1 for display
+ * purpose only, for example, displayed in Phone Status. It won't change
+ * the actual MSISDN/MDN. This setting won't be persisted during power cycle
+ * and it should be set again after reboot.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ *
+ * @param subId the subscriber that the alphatag and dialing number belongs to.
+ * @param alphaTag alpha-tagging of the dailing nubmer
+ * @param number The dialing number
+ *
+ * @hide
+ */
+ public void setLine1NumberForDisplay(long subId, String alphaTag, String number) {
+ try {
+ getITelephony().setLine1NumberForDisplay(subId, alphaTag, number);
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ }
+
+ /**
* Returns the alphabetic identifier associated with the line 1 number.
* Return null if it is unavailable.
* <p>
@@ -1730,6 +1780,15 @@
*/
/** {@hide} */
public String getLine1AlphaTag(long subId) {
+ String alphaTag = null;
+ try {
+ alphaTag = getITelephony().getLine1AlphaTagForDisplay(subId);
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ if (alphaTag != null) {
+ return alphaTag;
+ }
try {
return getSubscriberInfo().getLine1AlphaTagUsingSubId(subId);
} catch (RemoteException ex) {
@@ -2870,6 +2929,19 @@
/** @hide */
@SystemApi
+ public int checkCarrierPrivilegesForPackage(String pkgname) {
+ try {
+ return getITelephony().checkCarrierPrivilegesForPackage(pkgname);
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "hasCarrierPrivileges RemoteException", ex);
+ } catch (NullPointerException ex) {
+ Rlog.e(TAG, "hasCarrierPrivileges NPE", ex);
+ }
+ return CARRIER_PRIVILEGE_STATUS_NO_ACCESS;
+ }
+
+ /** @hide */
+ @SystemApi
public void dial(String number) {
try {
getITelephony().dial(number);
@@ -3171,4 +3243,73 @@
Log.e(TAG, "Error calling ITelephony#setSystemDefault", e);
}
}
+
+ /**
+ * Set whether Android should display a simplified Mobile Network Settings UI.
+ * The setting won't be persisted during power cycle.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ *
+ * @param enable true means enabling the simplified UI.
+ *
+ * @hide
+ */
+ public void enableSimplifiedNetworkSettings(boolean enable) {
+ enableSimplifiedNetworkSettings(getDefaultSubscription(), enable);
+ }
+
+ /**
+ * Set whether Android should display a simplified Mobile Network Settings UI.
+ * The setting won't be persisted during power cycle.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+ *
+ * @param subId for which the simplified UI should be enabled or disabled.
+ * @param enable true means enabling the simplified UI.
+ *
+ * @hide
+ */
+ public void enableSimplifiedNetworkSettings(long subId, boolean enable) {
+ try {
+ getITelephony().enableSimplifiedNetworkSettings(subId, enable);
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ }
+
+ /**
+ * Get whether a simplified Mobile Network Settings UI is enabled.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ *
+ * @return true if the simplified UI is enabled.
+ *
+ * @hide
+ */
+ public boolean getSimplifiedNetworkSettingsEnabled() {
+ return getSimplifiedNetworkSettingsEnabled(getDefaultSubscription());
+ }
+
+ /**
+ * Get whether a simplified Mobile Network Settings UI is enabled.
+ * <p>
+ * Requires Permission:
+ * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ *
+ * @param subId for which the simplified UI should be enabled or disabled.
+ * @return true if the simplified UI is enabled.
+ *
+ * @hide
+ */
+ public boolean getSimplifiedNetworkSettingsEnabled(long subId) {
+ try {
+ return getITelephony().getSimplifiedNetworkSettingsEnabled(subId);
+ } catch (RemoteException ex) {
+ } catch (NullPointerException ex) {
+ }
+ return false;
+ }
}
diff --git a/telephony/java/com/android/ims/ImsConfigListener.aidl b/telephony/java/com/android/ims/ImsConfigListener.aidl
new file mode 100644
index 0000000..e827774
--- /dev/null
+++ b/telephony/java/com/android/ims/ImsConfigListener.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ims;
+
+/**
+ * Used by IMS config client to monitor the config operation results.
+ * {@hide}
+ */
+oneway interface ImsConfigListener {
+ /**
+ * Notifies client the value of the get operation result on the feature config item.
+ * The arguments are the same as passed to com.android.ims.ImsConfig#getFeatureValue.
+ *
+ * @param feature. as defined in com.android.ims.ImsConfig#FeatureConstants.
+ * @param network. as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+ * @param value. as defined in com.android.ims.ImsConfig#FeatureValueConstants.
+ * @param status. as defined in com.android.ims.ImsConfig#OperationStatusConstants.
+ * @return void.
+ */
+ void onGetFeatureResponse(int feature, int network, int value, int status);
+
+ /**
+ * Notifies client the set value operation result for feature config item.
+ * Used by clients that need to be notified the set operation result.
+ * The arguments are the same as passed to com.android.ims.ImsConfig#setFeatureValue.
+ * The arguments are repeated in the callback to enable the listener to understand
+ * which configuration attempt failed.
+ *
+ * @param feature. as defined in com.android.ims.ImsConfig#FeatureConstants.
+ * @param network. as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+ * @param value. as defined in com.android.ims.ImsConfig#FeatureValueConstants.
+ * @param status. as defined in com.android.ims.ImsConfig#OperationStatusConstants.
+ *
+ * @return void.
+ */
+ void onSetFeatureResponse(int feature, int network, int value, int status);
+}
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/internal/IImsConfig.aidl b/telephony/java/com/android/ims/internal/IImsConfig.aidl
new file mode 100644
index 0000000..e8d921e
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsConfig.aidl
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2013 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.ims.internal;
+
+import com.android.ims.ImsConfigListener;
+
+/**
+ * Provides APIs to get/set the IMS service capability/parameters.
+ * The parameters can be configured by operator and/or user.
+ * We define 4 storage locations for the IMS config items:
+ * 1) Default config:For factory out device or device after factory data reset,
+ * the default config is used to build the initial state of the master config value.
+ * 2) Provisioned value: as the parameters provisioned by operator need to be preserved
+ * across FDR(factory data reset)/BOTA(over the air software upgrade), the operator
+ * provisioned items should be stored in memory location preserved across FDR/BOTA.
+ * 3) Master value: as the provisioned value can override the user setting,
+ * and the master config are used by IMS stack. They should be stored in the
+ * storage based on IMS vendor implementations.
+ * 4) User setting: For items can be changed by both user/operator, the user
+ * setting should take effect in some cases. So the user setting should be stored in
+ * database like setting.db.
+ *
+ * Priority consideration if both operator/user can config the same item:
+ * 1) For feature config items, the master value is obtained from the provisioned value
+ * masks with the user setting. Specifically the provisioned values overrides
+ * the user setting if feature is provisioned off. Otherwise, user setting takes
+ * effect.
+ * 2) For non-feature config item: to be implemented based on cases.
+ * Special cases considered as below:
+ * 1) Factory out device, the master configuration is built from default config.
+ * 2) For Factory data reset/SW upgrade device, the master config is built by
+ * taking provisioned value overriding default config.
+ * {@hide}
+ */
+interface IImsConfig {
+ /**
+ * Gets the value for ims service/capabilities parameters from the master
+ * value storage. Synchronous blocking call.
+ *
+ * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+ * @return value in Integer format.
+ */
+ int getMasterValue(int item);
+
+ /**
+ * Gets the value for ims service/capabilities parameters from the master
+ * value storage. Synchronous blocking call.
+ *
+ * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+ * @return value in String format.
+ */
+ String getMasterStringValue(int item);
+
+ /**
+ * Sets the value for IMS service/capabilities parameters by the operator device
+ * management entity. It sets the config item value in the provisioned storage
+ * from which the master value is derived. Synchronous blocking call.
+ *
+ * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+ * @param value in Integer format.
+ * @return void.
+ */
+ void setProvisionedValue(int item, int value);
+
+ /**
+ * Sets the value for IMS service/capabilities parameters by the operator device
+ * management entity. It sets the config item value in the provisioned storage
+ * from which the master value is derived. Synchronous blocking call.
+ *
+ * @param item, as defined in com.android.ims.ImsConfig#ConfigConstants.
+ * @param value in String format.
+ * @return void.
+ */
+ void setProvisionedStringValue(int item, String value);
+
+ /**
+ * Gets the value of the specified IMS feature item for specified network type.
+ * This operation gets the feature config value from the master storage (i.e. final
+ * value). Asynchronous non-blocking call.
+ *
+ * @param feature. as defined in com.android.ims.ImsConfig#FeatureConstants.
+ * @param network. as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+ * @param listener. feature value returned asynchronously through listener.
+ * @return void
+ */
+ oneway void getFeatureValue(int feature, int network, ImsConfigListener listener);
+
+ /**
+ * Sets the value for IMS feature item for specified network type.
+ * This operation stores the user setting in setting db from which master db
+ * is dervied.
+ *
+ * @param feature. as defined in com.android.ims.ImsConfig#FeatureConstants.
+ * @param network. as defined in android.telephony.TelephonyManager#NETWORK_TYPE_XXX.
+ * @param value. as defined in com.android.ims.ImsConfig#FeatureValueConstants.
+ * @param listener, provided if caller needs to be notified for set result.
+ * @return void
+ */
+ oneway void setFeatureValue(int feature, int network, int value, ImsConfigListener listener);
+}
diff --git a/telephony/java/com/android/ims/internal/IImsService.aidl b/telephony/java/com/android/ims/internal/IImsService.aidl
index bac5651..d992124 100644
--- a/telephony/java/com/android/ims/internal/IImsService.aidl
+++ b/telephony/java/com/android/ims/internal/IImsService.aidl
@@ -23,6 +23,7 @@
import com.android.ims.internal.IImsCallSession;
import com.android.ims.internal.IImsCallSessionListener;
import com.android.ims.internal.IImsUt;
+import com.android.ims.internal.IImsConfig;
/**
* {@hide}
@@ -45,4 +46,9 @@
* Ut interface for the supplementary service configuration.
*/
IImsUt getUtInterface(int serviceId);
+
+ /**
+ * Config interface to get/set IMS service/capability parameters.
+ */
+ IImsConfig getConfigInterface();
}
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index 745c9d0..07d2aae 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -24,6 +24,7 @@
import android.location.CountryDetector;
import android.net.Uri;
import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.PhoneLookup;
import android.provider.ContactsContract.RawContacts;
@@ -96,10 +97,18 @@
public String numberLabel;
public int photoResource;
- public long person_id;
+
+ // Contact ID, which will be 0 if a contact comes from the corp CP2.
+ public long contactIdOrZero;
public boolean needUpdate;
public Uri contactRefUri;
+ /**
+ * Contact display photo URI. If a contact has no display photo but a thumbnail, it'll be
+ * the thumbnail URI instead.
+ */
+ public Uri contactDisplayPhotoUri;
+
// fields to hold individual contact preference data,
// including the send to voicemail flag and the ringtone
// uri reference.
@@ -209,16 +218,29 @@
// Look for the person_id.
columnIndex = getColumnIndexForPersonId(contactRef, cursor);
if (columnIndex != -1) {
- info.person_id = cursor.getLong(columnIndex);
- if (VDBG) Rlog.v(TAG, "==> got info.person_id: " + info.person_id);
+ final long contactId = cursor.getLong(columnIndex);
+ if (contactId != 0 && !Contacts.isCorpContactId(contactId)) {
+ info.contactIdOrZero = contactId;
+ if (VDBG) {
+ Rlog.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero);
+ }
+ }
} else {
// No valid columnIndex, so we can't look up person_id.
- Rlog.w(TAG, "Couldn't find person_id column for " + contactRef);
+ Rlog.w(TAG, "Couldn't find contact_id column for " + contactRef);
// Watch out: this means that anything that depends on
// person_id will be broken (like contact photo lookups in
// the in-call UI, for example.)
}
+ // Display photo URI.
+ columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
+ if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
+ info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex));
+ } else {
+ info.contactDisplayPhotoUri = null;
+ }
+
// look for the custom ringtone, create from the string stored
// in the database.
columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE);
@@ -303,7 +325,8 @@
return new CallerInfo().markAsVoiceMail();
}
- Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
+ Uri contactUri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
+ Uri.encode(number));
CallerInfo info = getCallerInfo(context, contactUri);
info = doSecondaryLookupIfNecessary(context, number, info);
@@ -334,46 +357,13 @@
String username = PhoneNumberUtils.getUsernameFromUriNumber(number);
if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
previousResult = getCallerInfo(context,
- Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+ Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
Uri.encode(username)));
}
}
return previousResult;
}
- /**
- * getCallerId: a convenience method to get the caller id for a given
- * number.
- *
- * @param context the context used to get the ContentResolver.
- * @param number a phone number.
- * @return if the number belongs to a contact, the contact's name is
- * returned; otherwise, the number itself is returned.
- *
- * TODO NOTE: This MAY need to refer to the Asynchronous Query API
- * [startQuery()], instead of getCallerInfo, but since it looks like
- * it is only being used by the provider calls in the messaging app:
- * 1. android.provider.Telephony.Mms.getDisplayAddress()
- * 2. android.provider.Telephony.Sms.getDisplayAddress()
- * We may not need to make the change.
- */
- public static String getCallerId(Context context, String number) {
- CallerInfo info = getCallerInfo(context, number);
- String callerID = null;
-
- if (info != null) {
- String name = info.name;
-
- if (!TextUtils.isEmpty(name)) {
- callerID = name;
- } else {
- callerID = number;
- }
- }
-
- return callerID;
- }
-
// Accessors
/**
@@ -636,10 +626,10 @@
.append("\nnumberType: " + numberType)
.append("\nnumberLabel: " + numberLabel)
.append("\nphotoResource: " + photoResource)
- .append("\nperson_id: " + person_id)
+ .append("\ncontactIdOrZero: " + contactIdOrZero)
.append("\nneedUpdate: " + needUpdate)
- .append("\ncontactRefUri: " + contactRefUri)
- .append("\ncontactRingtoneUri: " + contactRefUri)
+ .append("\ncontactRingtoneUri: " + contactRingtoneUri)
+ .append("\ncontactDisplayPhotoUri: " + contactDisplayPhotoUri)
.append("\nshouldSendToVoicemail: " + shouldSendToVoicemail)
.append("\ncachedPhoto: " + cachedPhoto)
.append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent)
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index fe403d9..120356b 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -376,51 +376,14 @@
// Construct the URI object and query params, and start the query.
- Uri contactRef;
- String selection;
- String[] selectionArgs;
-
- if (PhoneNumberUtils.isUriNumber(number)) {
- // "number" is really a SIP address.
- if (DBG) Rlog.d(LOG_TAG, " - Treating number as a SIP address: " + /*number*/ "xxxxxxx");
-
- // We look up SIP addresses directly in the Data table:
- contactRef = Data.CONTENT_URI;
-
- // Note Data.DATA1 and SipAddress.SIP_ADDRESS are equivalent.
- //
- // Also note we use "upper(data1)" in the WHERE clause, and
- // uppercase the incoming SIP address, in order to do a
- // case-insensitive match.
- //
- // TODO: need to confirm that the use of upper() doesn't
- // prevent us from using the index! (Linear scan of the whole
- // contacts DB can be very slow.)
- //
- // TODO: May also need to normalize by adding "sip:" as a
- // prefix, if we start storing SIP addresses that way in the
- // database.
-
- selection = "upper(" + Data.DATA1 + ")=?"
- + " AND "
- + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
- selectionArgs = new String[] { number.toUpperCase() };
-
- } else {
- // "number" is a regular phone number. Use the PhoneLookup table:
- contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
- selection = null;
- selectionArgs = null;
- }
+ final Uri contactRef = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon()
+ .appendPath(number)
+ .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS,
+ String.valueOf(PhoneNumberUtils.isUriNumber(number)))
+ .build();
if (DBG) {
Rlog.d(LOG_TAG, "==> contactRef: " + sanitizeUriToString(contactRef));
- Rlog.d(LOG_TAG, "==> selection: " + selection);
- if (selectionArgs != null) {
- for (int i = 0; i < selectionArgs.length; i++) {
- Rlog.d(LOG_TAG, "==> selectionArgs[" + i + "]: " + selectionArgs[i]);
- }
- }
}
CallerInfoAsyncQuery c = new CallerInfoAsyncQuery();
@@ -446,8 +409,8 @@
cw, // cookie
contactRef, // uri
null, // projection
- selection, // selection
- selectionArgs, // selectionArgs
+ null, // selection
+ null, // selectionArgs
null); // orderBy
return c;
}
diff --git a/telephony/java/com/android/internal/telephony/IMms.aidl b/telephony/java/com/android/internal/telephony/IMms.aidl
index 4da90a5..db93a4f 100644
--- a/telephony/java/com/android/internal/telephony/IMms.aidl
+++ b/telephony/java/com/android/internal/telephony/IMms.aidl
@@ -17,6 +17,8 @@
package com.android.internal.telephony;
import android.app.PendingIntent;
+import android.content.ContentValues;
+import android.net.Uri;
/**
* Service interface to handle MMS API requests
@@ -25,25 +27,28 @@
/**
* Send an MMS message
*
+ * @param subId the SIM id
* @param callingPkg the package name of the calling app
* @param pdu the MMS message encoded in standard MMS PDU format
* @param locationUrl the optional location url for where this message should be sent to
* @param sentIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully sent, or failed
*/
- void sendMessage(String callingPkg, in byte[] pdu, String locationUrl,
+ void sendMessage(long subId, String callingPkg, in byte[] pdu, String locationUrl,
in PendingIntent sentIntent);
/**
* Download an MMS message using known location and transaction id
*
+ * @param subId the SIM id
* @param callingPkg the package name of the calling app
* @param locationUrl the location URL of the MMS message to be downloaded, usually obtained
* from the MMS WAP push notification
* @param downloadedIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is downloaded, or the download is failed
*/
- void downloadMessage(String callingPkg, String locationUrl, in PendingIntent downloadedIntent);
+ void downloadMessage(long subId, String callingPkg, String locationUrl,
+ in PendingIntent downloadedIntent);
/**
* Update the status of a pending (send-by-IP) MMS message handled by the carrier app.
@@ -65,4 +70,149 @@
* will be downloaded via carrier network
*/
void updateMmsDownloadStatus(int messageRef, in byte[] pdu);
+
+ /**
+ * Get carrier-dependent configuration value as boolean. For example, if multipart SMS
+ * is supported.
+ *
+ * @param name the configuration name
+ * @param defaultValue the default value if fail to find the name
+ */
+ boolean getCarrierConfigBoolean(String name, boolean defaultValue);
+
+ /**
+ * Get carrier-dependent configuration value as int. For example, the MMS message size limit.
+ *
+ * @param name the configuration name
+ * @param defaultValue the default value if fail to find the name
+ */
+ int getCarrierConfigInt(String name, int defaultValue);
+
+ /**
+ * Get carrier-dependent configuration value as String. For example, extra HTTP headers for
+ * MMS request.
+ *
+ * @param name the configuration name
+ * @param defaultValue the default value if fail to find the name
+ */
+ String getCarrierConfigString(String name, String defaultValue);
+
+ /**
+ * Set carrier-dependent configuration value as boolean. For example, if multipart SMS
+ * is supported.
+ *
+ * @param name the configuration name
+ * @param value the configuration value
+ */
+ void setCarrierConfigBoolean(String callingPkg, String name, boolean value);
+
+ /**
+ * Set carrier-dependent configuration value as int. For example, the MMS message size limit.
+ *
+ * @param name the configuration name
+ * @param value the configuration value
+ */
+ void setCarrierConfigInt(String callingPkg, String name, int value);
+
+ /**
+ * Set carrier-dependent configuration value as String. For example, extra HTTP headers for
+ * MMS request.
+ *
+ * @param name the configuration name
+ * @param value the configuration value
+ */
+ void setCarrierConfigString(String callingPkg, String name, String value);
+
+ /**
+ * Import a text message into system's SMS store
+ *
+ * @param callingPkg the calling app's package name
+ * @param address the destination address of the message
+ * @param type the type of the message
+ * @param text the message text
+ * @param timestampMillis the message timestamp in milliseconds
+ * @param seen if the message is seen
+ * @param read if the message is read
+ * @return the message URI, null if failed
+ */
+ Uri importTextMessage(String callingPkg, String address, int type, String text,
+ long timestampMillis, boolean seen, boolean read);
+
+ /**
+ * Import a multimedia message into system's MMS store
+ *
+ * @param callingPkg the package name of the calling app
+ * @param pdu the PDU of the message to import
+ * @param messageId the optional message id
+ * @param timestampSecs the message timestamp in seconds
+ * @param seen if the message is seen
+ * @param read if the message is read
+ * @return the message URI, null if failed
+ */
+ Uri importMultimediaMessage(String callingPkg, in byte[] pdu, String messageId,
+ long timestampSecs, boolean seen, boolean read);
+
+ /**
+ * Delete a system stored SMS or MMS message
+ *
+ * @param callingPkg the package name of the calling app
+ * @param messageUri the URI of the stored message
+ * @return true if deletion is successful, false otherwise
+ */
+ boolean deleteStoredMessage(String callingPkg, in Uri messageUri);
+
+ /**
+ * Delete a system stored SMS or MMS thread
+ *
+ * @param callingPkg the package name of the calling app
+ * @param conversationId the ID of the message conversation
+ * @return true if deletion is successful, false otherwise
+ */
+ boolean deleteStoredConversation(String callingPkg, long conversationId);
+
+ /**
+ * Update the status properties of a system stored SMS or MMS message, e.g.
+ * the read status of a message, etc.
+ *
+ * @param callingPkg the package name of the calling app
+ * @param messageUri the URI of the stored message
+ * @param statusValues a list of status properties in key-value pairs to update
+ * @return true if deletion is successful, false otherwise
+ */
+ boolean updateStoredMessageStatus(String callingPkg, in Uri messageUri,
+ in ContentValues statusValues);
+
+ /**
+ * Add a text message draft to system SMS store
+ *
+ * @param callingPkg the package name of the calling app
+ * @param address the destination address of message
+ * @param text the body of the message to send
+ * @return the URI of the stored draft message
+ */
+ Uri addTextMessageDraft(String callingPkg, String address, String text);
+
+ /**
+ * Add a multimedia message draft to system MMS store
+ *
+ * @param callingPkg the package name of the calling app
+ * @param pdu the PDU data of the draft MMS
+ * @return the URI of the stored draft message
+ */
+ Uri addMultimediaMessageDraft(String callingPkg, in byte[] pdu);
+
+ /**
+ * Send a system stored MMS message
+ *
+ * This is used for sending a previously sent, but failed-to-send, message or
+ * for sending a text message that has been stored as a draft.
+ *
+ * @param subId the SIM id
+ * @param callingPkg the package name of the calling app
+ * @param messageUri the URI of the stored message
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed
+ */
+ void sendStoredMessage(long subId, String callingPkg, in Uri messageUri,
+ in PendingIntent sentIntent);
}
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 53429b6..abbdc4a 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -17,6 +17,7 @@
package com.android.internal.telephony;
import android.app.PendingIntent;
+import android.net.Uri;
import com.android.internal.telephony.SmsRawData;
/** Interface for applications to access the ICC phone book.
@@ -466,7 +467,6 @@
*/
long getPreferredSmsSubscription();
-
/**
* Gets SMS format supported on IMS. SMS over IMS format is
* either 3GPP or 3GPP2.
@@ -491,12 +491,76 @@
*/
String getImsSmsFormatUsingSubId(long subId);
-
-
-
/*
* Get SMS prompt property, enabled or not
* @return true if enabled, false otherwise
*/
boolean isSMSPromptEnabled();
+
+ /**
+ * Send a system stored text message.
+ *
+ * This is used for sending a previously sent, but failed-to-send, message or
+ * for sending a text message that has been stored as a draft.
+ *
+ * @param subId the SIM id.
+ * @param callingPkg the package name of the calling app
+ * @param messageUri the URI of the stored message
+ * @param scAddress is the service center address or null to use the current default SMSC
+ * @param sentIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is successfully sent, or failed.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> the sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntent if not NULL this <code>PendingIntent</code> is
+ * broadcast when the message is delivered to the recipient. The
+ * raw pdu of the status report is in the extended data ("pdu").
+ */
+ void sendStoredText(long subId, String callingPkg, in Uri messageUri, String scAddress,
+ in PendingIntent sentIntent, in PendingIntent deliveryIntent);
+
+ /**
+ * Send a system stored multi-part text message.
+ *
+ * This is used for sending a previously sent, but failed-to-send, message or
+ * for sending a text message that has been stored as a draft.
+ * The provided <code>PendingIntent</code> lists should match the part number of the
+ * divided text of the stored message by using <code>divideMessage</code>
+ *
+ * @param subId the SIM id.
+ * @param callingPkg the package name of the calling app
+ * @param messageUri the URI of the stored message
+ * @param scAddress is the service center address or null to use
+ * the current default SMSC
+ * @param sentIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been sent.
+ * The result code will be <code>Activity.RESULT_OK</code> for success,
+ * or one of these errors:<br>
+ * <code>RESULT_ERROR_GENERIC_FAILURE</code><br>
+ * <code>RESULT_ERROR_RADIO_OFF</code><br>
+ * <code>RESULT_ERROR_NULL_PDU</code><br>
+ * For <code>RESULT_ERROR_GENERIC_FAILURE</code> each sentIntent may include
+ * the extra "errorCode" containing a radio technology specific value,
+ * generally only useful for troubleshooting.<br>
+ * The per-application based SMS control checks sentIntent. If sentIntent
+ * is NULL the caller will be checked against all unknown applications,
+ * which cause smaller number of SMS to be sent in checking period.
+ * @param deliveryIntents if not null, an <code>ArrayList</code> of
+ * <code>PendingIntent</code>s (one for each message part) that is
+ * broadcast when the corresponding message part has been delivered
+ * to the recipient. The raw pdu of the status report is in the
+ * extended data ("pdu").
+ */
+ void sendStoredMultipartText(long subId, String callingPkg, in Uri messageUri,
+ String scAddress, in List<PendingIntent> sentIntents,
+ in List<PendingIntent> deliveryIntents);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 7bf3b3f..4592717 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -653,5 +653,58 @@
* @return carrier privelege status defined in TelephonyManager.
*/
int hasCarrierPrivileges();
+
+ /**
+ * Similar to above, but check for pkg whose name is pkgname.
+ */
+ int checkCarrierPrivilegesForPackage(String pkgname);
+
+ /**
+ * Set whether Android should display a simplified Mobile Network Settings UI.
+ * The setting won't be persisted during power cycle.
+ *
+ * @param subId for which the simplified UI should be enabled or disabled.
+ * @param enable true means enabling the simplified UI.
+ */
+ void enableSimplifiedNetworkSettings(long subId, boolean enable);
+
+ /**
+ * Get whether a simplified Mobile Network Settings UI is enabled.
+ *
+ * @param subId for which the simplified UI should be enabled or disabled.
+ * @return true if the simplified UI is enabled.
+ */
+ boolean getSimplifiedNetworkSettingsEnabled(long subId);
+
+ /**
+ * Set the phone number string and its alphatag for line 1 for display
+ * purpose only, for example, displayed in Phone Status. It won't change
+ * the actual MSISDN/MDN. This setting won't be persisted during power cycle
+ * and it should be set again after reboot.
+ *
+ * @param subId the subscriber that the alphatag and dialing number belongs to.
+ * @param alphaTag alpha-tagging of the dailing nubmer
+ * @param number The dialing number
+ */
+ void setLine1NumberForDisplay(long subId, String alphaTag, String number);
+
+ /**
+ * Returns the displayed dialing number string if it was set previously via
+ * {@link #setLine1NumberForDisplay}. Otherwise returns null.
+ *
+ * @param subId whose dialing number for line 1 is returned.
+ * @return the displayed dialing number if set, or null if not set.
+ */
+ String getLine1NumberForDisplay(long subId);
+
+ /**
+ * Returns the displayed alphatag of the dialing number if it was set
+ * previously via {@link #setLine1NumberForDisplay}. Otherwise returns null.
+ *
+ * @param subId whose alphatag associated with line 1 is returned.
+ * @return the displayed alphatag of the dialing number if set, or null if
+ * not set.
+ */
+ String getLine1AlphaTagForDisplay(long subId);
}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
index 90db818..fbe7856 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GradientsActivity.java
@@ -223,12 +223,11 @@
float left = 40.0f;
float bottom = 40.0f + mDrawHeight;
- mPaint.setShader(mGradient);
-
mMatrix.setScale(1, mDrawWidth);
mMatrix.postRotate(90);
mMatrix.postTranslate(right, top);
mGradient.setLocalMatrix(mMatrix);
+ mPaint.setShader(mGradient);
canvas.drawRect(right - mDrawWidth, top, right, top + mDrawHeight, mPaint);
top += 40.0f + mDrawHeight;
@@ -237,6 +236,7 @@
mMatrix.setScale(1, mDrawHeight);
mMatrix.postTranslate(left, top);
mGradient.setLocalMatrix(mMatrix);
+ mPaint.setShader(mGradient);
canvas.drawRect(left, top, right, top + mDrawHeight, mPaint);
left += 40.0f + mDrawWidth;
@@ -248,6 +248,7 @@
mMatrix.postRotate(180);
mMatrix.postTranslate(left, bottom);
mGradient.setLocalMatrix(mMatrix);
+ mPaint.setShader(mGradient);
canvas.drawRect(left, bottom - mDrawHeight, right, bottom, mPaint);
top += 40.0f + mDrawHeight;
@@ -257,6 +258,7 @@
mMatrix.postRotate(-90);
mMatrix.postTranslate(left, top);
mGradient.setLocalMatrix(mMatrix);
+ mPaint.setShader(mGradient);
canvas.drawRect(left, top, left + mDrawWidth, bottom, mPaint);
right = left + mDrawWidth;
@@ -264,12 +266,11 @@
top = bottom + 20.0f;
bottom = top + 50.0f;
- mPaint.setShader(mGradientStops);
-
mMatrix.setScale(1, mDrawWidth);
mMatrix.postRotate(90);
mMatrix.postTranslate(right, top);
mGradientStops.setLocalMatrix(mMatrix);
+ mPaint.setShader(mGradientStops);
canvas.drawRect(left, top, right, bottom, mPaint);
canvas.restore();
diff --git a/tests/VoiceInteraction/res/layout/test_interaction.xml b/tests/VoiceInteraction/res/layout/test_interaction.xml
index 4c0c67a..d55736f 100644
--- a/tests/VoiceInteraction/res/layout/test_interaction.xml
+++ b/tests/VoiceInteraction/res/layout/test_interaction.xml
@@ -35,6 +35,13 @@
android:textColor="#ffffffff"
/>
+ <Button android:id="@+id/complete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="@string/completeVoice"
+ />
+
<Button android:id="@+id/abort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
index 142d781..002f350 100644
--- a/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
+++ b/tests/VoiceInteraction/res/layout/voice_interaction_session.xml
@@ -50,6 +50,11 @@
android:layout_height="wrap_content"
android:text="@string/confirm"
/>
+ <Button android:id="@+id/complete"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/complete"
+ />
<Button android:id="@+id/abort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/tests/VoiceInteraction/res/values/strings.xml b/tests/VoiceInteraction/res/values/strings.xml
index 70baa52..7eec90c 100644
--- a/tests/VoiceInteraction/res/values/strings.xml
+++ b/tests/VoiceInteraction/res/values/strings.xml
@@ -19,7 +19,9 @@
<string name="start">Start</string>
<string name="confirm">Confirm</string>
<string name="abort">Abort</string>
+ <string name="complete">Complete</string>
<string name="abortVoice">Abort Voice</string>
+ <string name="completeVoice">Complete Voice</string>
</resources>
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
index c24a088..d20906e 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionSession.java
@@ -34,6 +34,7 @@
TextView mText;
Button mStartButton;
Button mConfirmButton;
+ Button mCompleteButton;
Button mAbortButton;
static final int STATE_IDLE = 0;
@@ -41,6 +42,7 @@
static final int STATE_CONFIRM = 2;
static final int STATE_COMMAND = 3;
static final int STATE_ABORT_VOICE = 4;
+ static final int STATE_COMPLETE_VOICE = 5;
int mState = STATE_IDLE;
Request mPendingRequest;
@@ -64,6 +66,8 @@
mStartButton.setOnClickListener(this);
mConfirmButton = (Button)mContentView.findViewById(R.id.confirm);
mConfirmButton.setOnClickListener(this);
+ mCompleteButton = (Button)mContentView.findViewById(R.id.complete);
+ mCompleteButton.setOnClickListener(this);
mAbortButton = (Button)mContentView.findViewById(R.id.abort);
mAbortButton.setOnClickListener(this);
updateState();
@@ -74,6 +78,7 @@
mStartButton.setEnabled(mState == STATE_IDLE);
mConfirmButton.setEnabled(mState == STATE_CONFIRM || mState == STATE_COMMAND);
mAbortButton.setEnabled(mState == STATE_ABORT_VOICE);
+ mCompleteButton.setEnabled(mState == STATE_COMPLETE_VOICE);
}
public void onClick(View v) {
@@ -95,6 +100,11 @@
mPendingRequest = null;
mState = STATE_IDLE;
updateState();
+ } else if (v== mCompleteButton) {
+ mPendingRequest.sendCompleteVoiceResult(null);
+ mPendingRequest = null;
+ mState = STATE_IDLE;
+ updateState();
}
}
@@ -114,6 +124,15 @@
}
@Override
+ public void onCompleteVoice(Caller caller, Request request, CharSequence message, Bundle extras) {
+ Log.i(TAG, "onCompleteVoice: message=" + message + " extras=" + extras);
+ mText.setText(message);
+ mPendingRequest = request;
+ mState = STATE_COMPLETE_VOICE;
+ updateState();
+ }
+
+ @Override
public void onAbortVoice(Caller caller, Request request, CharSequence message, Bundle extras) {
Log.i(TAG, "onAbortVoice: message=" + message + " extras=" + extras);
mText.setText(message);
diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
index 3ae6a36..d64eefa 100644
--- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
+++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/TestInteractionActivity.java
@@ -30,6 +30,7 @@
VoiceInteractor mInteractor;
Button mAbortButton;
+ Button mCompleteButton;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -44,6 +45,8 @@
setContentView(R.layout.test_interaction);
mAbortButton = (Button)findViewById(R.id.abort);
mAbortButton.setOnClickListener(this);
+ mCompleteButton = (Button)findViewById(R.id.complete);
+ mCompleteButton.setOnClickListener(this);
// Framework should take care of these.
getWindow().setGravity(Gravity.TOP);
@@ -90,6 +93,21 @@
}
};
mInteractor.submitRequest(req);
+ } else if (v == mCompleteButton) {
+ VoiceInteractor.CompleteVoiceRequest req = new VoiceInteractor.CompleteVoiceRequest(
+ "Woohoo, completed!", null) {
+ @Override
+ public void onCancel() {
+ Log.i(TAG, "Canceled!");
+ }
+
+ @Override
+ public void onCompleteResult(Bundle result) {
+ Log.i(TAG, "Complete result: result=" + result);
+ getActivity().finish();
+ }
+ };
+ mInteractor.submitRequest(req);
}
}
diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
index 105803e..f939678 100644
--- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
+++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java
@@ -828,9 +828,18 @@
@Override
public int[] extractThemeAttrs() {
+ // The drawables are always inflated with a Theme and we don't care about caching. So,
+ // just return.
return null;
}
+ @Override
+ public int getChangingConfigurations() {
+ // We don't care about caching. Any change in configuration is a fresh render. So,
+ // just return.
+ return 0;
+ }
+
/**
* Retrieve the raw TypedValue for the attribute at <var>index</var>.
*
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index bd88ae2..f4282ad 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -53,6 +53,7 @@
*/
public final class Bitmap_Delegate {
+
public enum BitmapCreateFlags {
PREMULTIPLIED, MUTABLE
}
@@ -68,6 +69,7 @@
private BufferedImage mImage;
private boolean mHasAlpha = true;
private boolean mHasMipMap = false; // TODO: check the default.
+ private boolean mIsPremultiplied = true;
private int mGenerationId = 0;
@@ -393,21 +395,19 @@
}
@LayoutlibDelegate
- /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y,
- boolean isPremultiplied) {
+ /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y) {
// get the delegate from the native int.
Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
if (delegate == null) {
return 0;
}
- // TODO: Support isPremultiplied.
return delegate.mImage.getRGB(x, y);
}
@LayoutlibDelegate
/*package*/ static void nativeGetPixels(long nativeBitmap, int[] pixels, int offset,
- int stride, int x, int y, int width, int height, boolean isPremultiplied) {
+ int stride, int x, int y, int width, int height) {
Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
if (delegate == null) {
return;
@@ -418,8 +418,7 @@
@LayoutlibDelegate
- /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color,
- boolean isPremultiplied) {
+ /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color) {
Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
if (delegate == null) {
return;
@@ -430,7 +429,7 @@
@LayoutlibDelegate
/*package*/ static void nativeSetPixels(long nativeBitmap, int[] colors, int offset,
- int stride, int x, int y, int width, int height, boolean isPremultiplied) {
+ int stride, int x, int y, int width, int height) {
Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
if (delegate == null) {
return;
@@ -518,7 +517,26 @@
}
@LayoutlibDelegate
- /*package*/ static void nativeSetAlphaAndPremultiplied(long nativeBitmap, boolean hasAlpha,
+ /*package*/ static boolean nativeIsPremultiplied(long nativeBitmap) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ return delegate != null && delegate.mIsPremultiplied;
+
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) {
+ // get the delegate from the native int.
+ Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
+ if (delegate == null) {
+ return;
+ }
+
+ delegate.mIsPremultiplied = isPremul;
+ }
+
+ @LayoutlibDelegate
+ /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha,
boolean isPremul) {
// get the delegate from the native int.
Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap);
diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
index 887e8f6..f044def6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java
@@ -17,19 +17,14 @@
package android.graphics;
import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-import android.content.res.AssetManager;
import android.graphics.FontFamily_Delegate.FontVariant;
import java.awt.Font;
import java.io.File;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
@@ -58,10 +53,8 @@
// ---- delegate data ----
- @Nullable
+ @NonNull
private final FontFamily_Delegate[] mFontFamilies; // the reference to FontFamily_Delegate.
- @Nullable
- private final Font mFont;
/** @see FontFamily_Delegate.FontInfo#mStyle */
private final int mStyle;
@@ -87,11 +80,6 @@
@NonNull
public List<Font> getFonts(FontVariant variant) {
assert variant != FontVariant.NONE;
- if (mFontFamilies == null) {
- // We don't care about the variant here, since the Typeface was created with a single
- // Font File. So, we simply return that Font.
- return getFontAsList();
- }
List<Font> fonts = new ArrayList<Font>(mFontFamilies.length);
for (int i = 0; i < mFontFamilies.length; i++) {
FontFamily_Delegate ffd = mFontFamilies[i];
@@ -128,14 +116,6 @@
// ---- native methods ----
@LayoutlibDelegate
- /*package*/ static synchronized long nativeCreate(String familyName, int style) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Could not find font with family \"" + familyName + "\".",
- null /*throwable*/, null /*data*/);
- return 0;
- }
-
- @LayoutlibDelegate
/*package*/ static synchronized long nativeCreateFromTypeface(long native_instance, int style) {
Typeface_Delegate delegate = sManager.getDelegate(native_instance);
if (delegate == null) {
@@ -149,24 +129,6 @@
}
@LayoutlibDelegate
- /*package*/ static synchronized long nativeCreateFromAsset(AssetManager mgr, String path) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Typeface.createFromAsset() is not supported.", null /*throwable*/, null /*data*/);
- return 0;
- }
-
- @LayoutlibDelegate
- /*package*/ static synchronized long nativeCreateFromFile(String path) {
- if (!path.startsWith(SYSTEM_FONTS)) {
- Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
- "Typeface.createFromFile() can only work with platform fonts located in " +
- SYSTEM_FONTS, null, null);
- return 0;
- }
- return sManager.addNewDelegate(new Typeface_Delegate(path));
- }
-
- @LayoutlibDelegate
/*package*/ static synchronized long nativeCreateFromArray(long[] familyArray) {
FontFamily_Delegate[] fontFamilies = new FontFamily_Delegate[familyArray.length];
for (int i = 0; i < familyArray.length; i++) {
@@ -203,22 +165,8 @@
// ---- Private delegate/helper methods ----
- private Typeface_Delegate(FontFamily_Delegate[] fontFamilies, int style) {
+ private Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style) {
mFontFamilies = fontFamilies;
- mFont = null;
mStyle = style;
}
-
- private Typeface_Delegate(String path) {
- mFont = FontFamily_Delegate.loadFont(path);
- mFontFamilies = null;
- mStyle = FontFamily_Delegate.getFontStyle(path);
- }
-
- private List<Font> getFontAsList() {
- if (mFont != null) {
- return Collections.singletonList(mFont);
- }
- return Collections.emptyList();
- }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
index 9ec6f4d..c44a57c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -237,9 +237,4 @@
// TODO Auto-generated method stub
return null;
}
-
- @Override
- public void setCursorAnchorMonitorMode(IBinder arg0, int arg1) {
- // TODO Auto-generated method stub
- }
}
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index 32625ae..8de64db 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -131,8 +131,8 @@
Mac has horrible font rendering support.
ReplaceMethodCallsAdapter replaces calls to certain methods. Currently, it only rewrites calls to
-java.lang.System.arraycopy([CI[CII)V, which is not part of the Desktop VM to call the more general
-method java.lang.System.arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V.
+specialized versions of java.lang.System.arraycopy(), which are not part of the Desktop VM to call
+the more general method java.lang.System.arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V.
The ClassAdapters are chained together to achieve the desired output. (Look at section 2.2.7
Transformation chains in the asm user guide, link in the References.) The order of execution of
@@ -236,7 +236,7 @@
implementation.
b- A brand new implementation of SomeClass.MethodName() which calls to a non-existing static method
named SomeClass_Delegate.MethodName(). The implementation of this 'delegate' method is done in
-layoutlib_brigde.
+layoutlib_bridge.
The delegate method is a static method. If the original method is non-static, the delegate method
receives the original 'this' as its first argument. If the original method is an inner non-static
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
index 8373e30..767e597 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -724,9 +724,9 @@
considerDesc(desc);
- // Check if method is java.lang.System.arrayCopy([CI[CII)V
+ // Check if method is a specialized version of java.lang.System.arrayCopy()
if (owner.equals("java/lang/System") && name.equals("arraycopy")
- && desc.equals("([CI[CII)V")) {
+ && !desc.equals("(Ljava/lang/Object;ILjava/lang/Object;II)V")) {
mReplaceMethodCallClasses.add(mOwnerClass);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index c96a143..bd6f070 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -216,21 +216,21 @@
String name = classToEntryPath(clazz);
InputStream is = ClassLoader.getSystemResourceAsStream(name);
ClassReader cr = new ClassReader(is);
- byte[] b = transform(cr, true /* stubNativesOnly */);
+ byte[] b = transform(cr, true);
name = classNameToEntryPath(transformName(cr.getClassName()));
all.put(name, b);
}
for (Entry<String, ClassReader> entry : mDeps.entrySet()) {
ClassReader cr = entry.getValue();
- byte[] b = transform(cr, true /* stubNativesOnly */);
+ byte[] b = transform(cr, true);
String name = classNameToEntryPath(transformName(cr.getClassName()));
all.put(name, b);
}
for (Entry<String, ClassReader> entry : mKeep.entrySet()) {
ClassReader cr = entry.getValue();
- byte[] b = transform(cr, true /* stubNativesOnly */);
+ byte[] b = transform(cr, true);
String name = classNameToEntryPath(transformName(cr.getClassName()));
all.put(name, b);
}
@@ -282,7 +282,7 @@
/**
* Utility method to get the JAR entry path from a Class name.
- * e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
+ * e.g. it returns something like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
*/
private String classToEntryPath(Class<?> clazz) {
String name = "";
@@ -345,10 +345,8 @@
cv = new RenameClassAdapter(cv, className, newName);
}
- cv = new TransformClassAdapter(mLog, mStubMethods,
- mDeleteReturns.get(className),
- newName, cv,
- stubNativesOnly, stubNativesOnly || hasNativeMethods);
+ cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className),
+ newName, cv, stubNativesOnly);
Set<String> delegateMethods = mDelegateMethods.get(className);
if (delegateMethods != null && !delegateMethods.isEmpty()) {
@@ -361,7 +359,7 @@
}
}
- cr.accept(cv, 0 /* flags */);
+ cr.accept(cv, 0);
return cw.toByteArray();
}
@@ -395,7 +393,7 @@
*/
boolean hasNativeMethods(ClassReader cr) {
ClassHasNativeVisitor cv = new ClassHasNativeVisitor();
- cr.accept(cv, 0 /* flags */);
+ cr.accept(cv, 0);
return cv.hasNativeMethods();
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index 927be97..3d89c68 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -32,10 +32,10 @@
/** Suffix added to original methods. */
private static final String ORIGINAL_SUFFIX = "_Original";
- private static String CONSTRUCTOR = "<init>";
- private static String CLASS_INIT = "<clinit>";
+ private static final String CONSTRUCTOR = "<init>";
+ private static final String CLASS_INIT = "<clinit>";
- public final static String ALL_NATIVES = "<<all_natives>>";
+ public static final String ALL_NATIVES = "<<all_natives>>";
private final String mClassName;
private final Set<String> mDelegateMethods;
@@ -78,19 +78,16 @@
mDelegateMethods.contains(name);
if (!useDelegate) {
- // Not creating a delegate for this method, pass it as-is from the reader
- // to the writer.
+ // Not creating a delegate for this method, pass it as-is from the reader to the writer.
return super.visitMethod(access, name, desc, signature, exceptions);
}
- if (useDelegate) {
- if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) {
- // We don't currently support generating delegates for constructors.
- throw new UnsupportedOperationException(
- String.format(
- "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$
- mClassName, name, desc));
- }
+ if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) {
+ // We don't currently support generating delegates for constructors.
+ throw new UnsupportedOperationException(
+ String.format(
+ "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$
+ mClassName, name, desc));
}
if (isNative) {
@@ -98,8 +95,8 @@
access = access & ~Opcodes.ACC_NATIVE;
MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
- DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
- mLog, null /*mwOriginal*/, mwDelegate, mClassName, name, desc, isStatic);
+ DelegateMethodAdapter a = new DelegateMethodAdapter(
+ mLog, null, mwDelegate, mClassName, name, desc, isStatic);
// A native has no code to visit, so we need to generate it directly.
a.generateDelegateCode();
@@ -112,22 +109,16 @@
// The content is the original method as-is from the reader.
// - A brand new implementation of SomeClass.MethodName() which calls to a
// non-existing method named SomeClass_Delegate.MethodName().
- // The implementation of this 'delegate' method is done in layoutlib_brigde.
+ // The implementation of this 'delegate' method is done in layoutlib_bridge.
int accessDelegate = access;
- // change access to public for the original one
- if (Main.sOptions.generatePublicAccess) {
- access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
- access |= Opcodes.ACC_PUBLIC;
- }
MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX,
desc, signature, exceptions);
MethodVisitor mwDelegate = super.visitMethod(accessDelegate, name,
desc, signature, exceptions);
- DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
+ return new DelegateMethodAdapter(
mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic);
- return a;
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
similarity index 97%
rename from tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java
rename to tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
index 0000b22..12690db 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -71,7 +71,7 @@
* Instances of this class are not re-usable.
* The class adapter creates a new instance for each method.
*/
-class DelegateMethodAdapter2 extends MethodVisitor {
+class DelegateMethodAdapter extends MethodVisitor {
/** Suffix added to delegate classes. */
public static final String DELEGATE_SUFFIX = "_Delegate";
@@ -97,10 +97,10 @@
private Object[] mDelegateLineNumber;
/**
- * Creates a new {@link DelegateMethodAdapter2} that will transform this method
+ * Creates a new {@link DelegateMethodAdapter} that will transform this method
* into a delegate call.
* <p/>
- * See {@link DelegateMethodAdapter2} for more details.
+ * See {@link DelegateMethodAdapter} for more details.
*
* @param log The logger object. Must not be null.
* @param mvOriginal The parent method writer to copy of the original method.
@@ -114,7 +114,7 @@
* {@link Type#getArgumentTypes(String)})
* @param isStatic True if the method is declared static.
*/
- public DelegateMethodAdapter2(Log log,
+ public DelegateMethodAdapter(Log log,
MethodVisitor mvOriginal,
MethodVisitor mvDelegate,
String className,
@@ -138,7 +138,7 @@
* (since they have no code to visit).
* <p/>
* Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
- * return this instance of {@link DelegateMethodAdapter2} and let the normal visitor pattern
+ * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
* invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
* this method will be invoked from {@link MethodVisitor#visitEnd()}.
*/
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index ad10656..ea9ce10 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -49,7 +49,6 @@
public class Main {
public static class Options {
- public boolean generatePublicAccess = true;
public boolean listAllDeps = false;
public boolean listOnlyMissingDeps = false;
}
@@ -196,8 +195,6 @@
for (String s : args) {
if (s.equals("-v")) {
log.setVerbose(true);
- } else if (s.equals("-p")) {
- sOptions.generatePublicAccess = false;
} else if (s.equals("--list-deps")) {
sOptions.listAllDeps = true;
needs_dest = false;
@@ -225,8 +222,6 @@
return false;
}
- sOptions.generatePublicAccess = false;
-
return true;
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index e57eba1..ae17417 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -20,10 +20,23 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
/**
* Replaces calls to certain methods that do not exist in the Desktop VM.
*/
public class ReplaceMethodCallsAdapter extends ClassVisitor {
+
+ /**
+ * Descriptors for specialized versions {@link System#arraycopy} that are not present on the
+ * Desktop VM.
+ */
+ private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<String>(Arrays.asList(
+ "([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V",
+ "([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V"));
+
public ReplaceMethodCallsAdapter(ClassVisitor cv) {
super(Opcodes.ASM4, cv);
}
@@ -42,10 +55,12 @@
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
- // Check if method is java.lang.System.arrayCopy([CI[CII)V
- if (owner.equals("java/lang/System") && name.equals("arraycopy")
- && desc.equals("([CI[CII)V")) {
- desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
+ // Check if method is a specialized version of java.lang.System.arrayCopy
+ if (owner.equals("java/lang/System") && name.equals("arraycopy")) {
+
+ if (ARRAYCOPY_DESCRIPTORS.contains(desc)) {
+ desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
+ }
}
super.visitMethodInsn(opcode, owner, name, desc);
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
index d45a183..0b869a5 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -40,19 +40,16 @@
/**
* Creates a new class adapter that will stub some or all methods.
- * @param logger
* @param stubMethods list of method signatures to always stub out
* @param deleteReturns list of types that trigger the deletion of methods returning them.
* @param className The name of the class being modified
* @param cv The parent class writer visitor
* @param stubNativesOnly True if only native methods should be stubbed. False if all
* methods should be stubbed.
- * @param hasNative True if the method has natives, in which case its access should be
- * changed.
*/
public TransformClassAdapter(Log logger, Set<String> stubMethods,
Set<String> deleteReturns, String className, ClassVisitor cv,
- boolean stubNativesOnly, boolean hasNative) {
+ boolean stubNativesOnly) {
super(Opcodes.ASM4, cv);
mLog = logger;
mStubMethods = stubMethods;
@@ -70,11 +67,6 @@
// This class might be being renamed.
name = mClassName;
- // remove protected or private and set as public
- if (Main.sOptions.generatePublicAccess) {
- access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
- access |= Opcodes.ACC_PUBLIC;
- }
// remove final
access = access & ~Opcodes.ACC_FINAL;
// note: leave abstract classes as such
@@ -87,11 +79,6 @@
/* Visits the header of an inner class. */
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
- // remove protected or private and set as public
- if (Main.sOptions.generatePublicAccess) {
- access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
- access |= Opcodes.ACC_PUBLIC;
- }
// remove final
access = access & ~Opcodes.ACC_FINAL;
// note: leave abstract classes as such
@@ -119,12 +106,6 @@
String methodSignature = mClassName.replace('/', '.') + "#" + name;
- // change access to public
- if (Main.sOptions.generatePublicAccess) {
- access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
- access |= Opcodes.ACC_PUBLIC;
- }
-
// remove final
access = access & ~Opcodes.ACC_FINAL;
@@ -159,11 +140,6 @@
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature,
Object value) {
- // change access to public
- if (Main.sOptions.generatePublicAccess) {
- access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
- access |= Opcodes.ACC_PUBLIC;
- }
return super.visitField(access, name, desc, signature, value);
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index 6e120ce..94aad1d 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -130,7 +130,7 @@
}
/**
- * {@link DelegateMethodAdapter2} does not support overriding constructors yet,
+ * {@link DelegateMethodAdapter} does not support overriding constructors yet,
* so this should fail with an {@link UnsupportedOperationException}.
*
* Although not tested here, the message of the exception should contain the
@@ -258,9 +258,8 @@
// Check the inner class. Since it's not a static inner class, we need
// to use the hidden constructor that takes the outer class as first parameter.
Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME);
- Constructor<?> innerCons = innerClazz2.getConstructor(
- new Class<?>[] { outerClazz2 });
- Object i2 = innerCons.newInstance(new Object[] { o2 });
+ Constructor<?> innerCons = innerClazz2.getConstructor(outerClazz2);
+ Object i2 = innerCons.newInstance(o2);
assertNotNull(i2);
// The original Inner.get returns 3+10+20,
@@ -344,10 +343,10 @@
*/
public int callGet(Object instance, int a, long b) throws Exception {
Method m = instance.getClass().getMethod("get",
- new Class<?>[] { int.class, long.class } );
+ int.class, long.class);
- Object result = m.invoke(instance, new Object[] { a, b });
- return ((Integer) result).intValue();
+ Object result = m.invoke(instance, a, b);
+ return (Integer) result;
}
/**
@@ -356,10 +355,10 @@
*/
public int callGet_Original(Object instance, int a, long b) throws Exception {
Method m = instance.getClass().getMethod("get_Original",
- new Class<?>[] { int.class, long.class } );
+ int.class, long.class);
- Object result = m.invoke(instance, new Object[] { a, b });
- return ((Integer) result).intValue();
+ Object result = m.invoke(instance, a, b);
+ return (Integer) result;
}
/**
@@ -388,10 +387,10 @@
*/
public int callAdd(Object instance, int a, int b) throws Exception {
Method m = instance.getClass().getMethod("add",
- new Class<?>[] { int.class, int.class });
+ int.class, int.class);
- Object result = m.invoke(instance, new Object[] { a, b });
- return ((Integer) result).intValue();
+ Object result = m.invoke(instance, a, b);
+ return (Integer) result;
}
/**
@@ -401,10 +400,10 @@
public int callCallNativeInstance(Object instance, int a, double d, Object[] o)
throws Exception {
Method m = instance.getClass().getMethod("callNativeInstance",
- new Class<?>[] { int.class, double.class, Object[].class });
+ int.class, double.class, Object[].class);
- Object result = m.invoke(instance, new Object[] { a, d, o });
- return ((Integer) result).intValue();
+ Object result = m.invoke(instance, a, d, o);
+ return (Integer) result;
}
public abstract void testModifiedInstance() throws Exception;
@@ -442,8 +441,8 @@
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
// next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw);
- Constructor<?> cons = tcvClass.getConstructor(new Class<?>[] { pw.getClass() });
- Object tcv = cons.newInstance(new Object[] { pw });
+ Constructor<?> cons = tcvClass.getConstructor(pw.getClass());
+ Object tcv = cons.newInstance(pw);
ClassReader cr2 = new ClassReader(bytes);
cr2.accept((ClassVisitor) tcv, 0 /* flags */);
@@ -452,8 +451,7 @@
}
// Re-throw exception with new message
- RuntimeException ex = new RuntimeException(sb.toString(), t);
- return ex;
+ return new RuntimeException(sb.toString(), t);
} catch (Throwable ignore) {
// In case of problem, just throw the original exception as-is.
return t;
diff --git a/wifi/java/android/net/wifi/WifiAdapter.java b/wifi/java/android/net/wifi/WifiAdapter.java
index f6ee730..c0211f3 100644
--- a/wifi/java/android/net/wifi/WifiAdapter.java
+++ b/wifi/java/android/net/wifi/WifiAdapter.java
@@ -19,34 +19,57 @@
import android.os.Parcel;
import android.os.Parcelable;
-/** @hide */
+/**
+ * Represents local wifi adapter. Different devices have different kinds of
+ * wifi adapters; each with different capabilities. Use this class to find out
+ * which capabilites are supported by the wifi adapter on the device.
+ */
public class WifiAdapter implements Parcelable {
/* Keep this list in sync with wifi_hal.h */
+ /** @hide */
public static final int WIFI_FEATURE_INFRA = 0x0001; // Basic infrastructure mode
+ /** @hide */
public static final int WIFI_FEATURE_INFRA_5G = 0x0002; // Support for 5 GHz Band
+ /** @hide */
public static final int WIFI_FEATURE_PASSPOINT = 0x0004; // Support for GAS/ANQP
+ /** @hide */
public static final int WIFI_FEATURE_P2P = 0x0008; // Wifi-Direct
+ /** @hide */
public static final int WIFI_FEATURE_MOBILE_HOTSPOT = 0x0010; // Soft AP
+ /** @hide */
public static final int WIFI_FEATURE_SCANNER = 0x0020; // WifiScanner APIs
+ /** @hide */
public static final int WIFI_FEATURE_NAN = 0x0040; // Neighbor Awareness Networking
+ /** @hide */
public static final int WIFI_FEATURE_D2D_RTT = 0x0080; // Device-to-device RTT
+ /** @hide */
public static final int WIFI_FEATURE_D2AP_RTT = 0x0100; // Device-to-AP RTT
+ /** @hide */
public static final int WIFI_FEATURE_BATCH_SCAN = 0x0200; // Batched Scan (deprecated)
+ /** @hide */
public static final int WIFI_FEATURE_PNO = 0x0400; // Preferred network offload
+ /** @hide */
public static final int WIFI_FEATURE_ADDITIONAL_STA = 0x0800; // Support for two STAs
+ /** @hide */
public static final int WIFI_FEATURE_TDLS = 0x1000; // Tunnel directed link setup
+ /** @hide */
public static final int WIFI_FEATURE_TDLS_OFFCHANNEL = 0x2000; // Support for TDLS off channel
+ /** @hide */
public static final int WIFI_FEATURE_EPR = 0x4000; // Enhanced power reporting
private String name;
private int supportedFeatures;
+ /** @hide */
public WifiAdapter(String name, int supportedFeatures) {
this.name = name;
this.supportedFeatures = supportedFeatures;
}
+ /**
+ * @return name of the adapter
+ */
public String getName() {
return name;
}
@@ -59,68 +82,121 @@
return (supportedFeatures & feature) == feature;
}
+ /**
+ * @return true if this adapter supports 5 GHz band
+ */
+ public boolean is5GHzBandSupported() {
+ return isFeatureSupported(WIFI_FEATURE_INFRA_5G);
+ }
+
+ /**
+ * @return true if this adapter supports passpoint
+ */
public boolean isPasspointSupported() {
return isFeatureSupported(WIFI_FEATURE_PASSPOINT);
}
- public boolean isWifiDirectSupported() {
+ /**
+ * @return true if this adapter supports WifiP2pManager (Wi-Fi Direct)
+ */
+ public boolean isP2pSupported() {
return isFeatureSupported(WIFI_FEATURE_P2P);
}
- public boolean isMobileHotstpoSupported() {
+ /**
+ * @return true if this adapter supports portable Wi-Fi hotspot
+ */
+ public boolean isPortableHotspotSupported() {
return isFeatureSupported(WIFI_FEATURE_MOBILE_HOTSPOT);
}
+ /**
+ * @return true if this adapter supports WifiScanner APIs
+ */
public boolean isWifiScannerSupported() {
return isFeatureSupported(WIFI_FEATURE_SCANNER);
}
+ /**
+ * @return true if this adapter supports Neighbour Awareness Network APIs
+ * @hide
+ */
public boolean isNanSupported() {
return isFeatureSupported(WIFI_FEATURE_NAN);
}
+ /**
+ * @return true if this adapter supports Device-to-device RTT
+ */
public boolean isDeviceToDeviceRttSupported() {
return isFeatureSupported(WIFI_FEATURE_D2D_RTT);
}
+ /**
+ * @return true if this adapter supports Device-to-AP RTT
+ */
public boolean isDeviceToApRttSupported() {
return isFeatureSupported(WIFI_FEATURE_D2AP_RTT);
}
+ /**
+ * @return true if this adapter supports offloaded connectivity scan
+ */
public boolean isPreferredNetworkOffloadSupported() {
return isFeatureSupported(WIFI_FEATURE_PNO);
}
+ /**
+ * @return true if this adapter supports multiple simultaneous connections
+ * @hide
+ */
public boolean isAdditionalStaSupported() {
return isFeatureSupported(WIFI_FEATURE_ADDITIONAL_STA);
}
+ /**
+ * @return true if this adapter supports Tunnel Directed Link Setup
+ */
public boolean isTdlsSupported() {
return isFeatureSupported(WIFI_FEATURE_TDLS);
}
+ /**
+ * @return true if this adapter supports Off Channel Tunnel Directed Link Setup
+ */
public boolean isOffChannelTdlsSupported() {
return isFeatureSupported(WIFI_FEATURE_TDLS_OFFCHANNEL);
}
+ /**
+ * @return true if this adapter supports advanced power/performance counters
+ */
public boolean isEnhancedPowerReportingSupported() {
return isFeatureSupported(WIFI_FEATURE_EPR);
}
/* Parcelable implementation */
-
- /** Implement the Parcelable interface {@hide} */
+ /**
+ * Implement the Parcelable interface
+ * {@hide}
+ */
public int describeContents() {
return 0;
}
- /** Implement the Parcelable interface {@hide} */
+ /**
+ * Implement the Parcelable interface
+ * {@hide}
+ */
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(supportedFeatures);
}
- /** Implement the Parcelable interface {@hide} */
+ /**
+ * Implement the Parcelable interface
+ * {@hide}
+ */
public static final Creator<WifiAdapter> CREATOR =
new Creator<WifiAdapter>() {
public WifiAdapter createFromParcel(Parcel in) {
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index f9a9e7d..cf4cb89 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -568,9 +568,10 @@
}
/**
- * @hide
+ * Retrieve all wifi adapters available on this device
+ * @return list of adapters
*/
- public List<WifiAdapter> getAdaptors() {
+ public List<WifiAdapter> getAdapters() {
try {
return mService.getAdaptors();
} catch (RemoteException e) {