summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/accounts/AccountManager.java50
-rw-r--r--core/java/android/accounts/IAccountManager.aidl1
-rw-r--r--core/java/android/animation/AnimatorInflater.java5
-rw-r--r--core/java/android/animation/FloatKeyframeSet.java7
-rw-r--r--core/java/android/animation/IntKeyframeSet.java7
-rw-r--r--core/java/android/animation/KeyframeSet.java8
-rw-r--r--core/java/android/animation/ObjectAnimator.java6
-rw-r--r--core/java/android/animation/TimeAnimator.java1
-rw-r--r--core/java/android/animation/ValueAnimator.java100
-rw-r--r--core/java/android/app/ActivityManager.java41
-rw-r--r--core/java/android/app/ActivityManagerNative.java31
-rw-r--r--core/java/android/app/ActivityThread.java124
-rw-r--r--core/java/android/app/AppOpsManager.java22
-rw-r--r--core/java/android/app/ApplicationErrorReport.java8
-rw-r--r--core/java/android/app/ApplicationPackageManager.java68
-rw-r--r--core/java/android/app/AssistStructure.java564
-rw-r--r--core/java/android/app/IActivityManager.java5
-rw-r--r--core/java/android/app/IBackupAgent.aidl5
-rw-r--r--core/java/android/app/Notification.java5
-rw-r--r--core/java/android/app/SystemServiceRegistry.java12
-rw-r--r--core/java/android/app/UiAutomation.java2
-rw-r--r--core/java/android/app/VoiceInteractor.java30
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java72
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl2
-rw-r--r--core/java/android/app/backup/BackupAgent.java55
-rw-r--r--core/java/android/app/backup/BackupTransport.java27
-rw-r--r--core/java/android/app/backup/FullBackup.java2
-rw-r--r--core/java/android/app/backup/FullBackupDataOutput.java19
-rw-r--r--core/java/android/app/backup/IBackupManager.aidl9
-rw-r--r--core/java/android/app/backup/RecentsBackupHelper.java16
-rw-r--r--core/java/android/app/usage/UsageStatsManagerInternal.java37
-rw-r--r--core/java/android/bluetooth/BluetoothAdapter.java34
-rw-r--r--core/java/android/content/Context.java11
-rw-r--r--core/java/android/content/Intent.java52
-rw-r--r--core/java/android/content/IntentFilter.java148
-rw-r--r--core/java/android/content/RestrictionEntry.java177
-rw-r--r--core/java/android/content/RestrictionsManager.java130
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java34
-rw-r--r--core/java/android/content/pm/IPackageInstaller.aidl3
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl16
-rw-r--r--core/java/android/content/pm/IntentFilterVerificationInfo.aidl19
-rw-r--r--core/java/android/content/pm/IntentFilterVerificationInfo.java225
-rw-r--r--core/java/android/content/pm/PackageInstaller.java7
-rw-r--r--core/java/android/content/pm/PackageManager.java221
-rw-r--r--core/java/android/content/pm/PackageParser.java80
-rw-r--r--core/java/android/content/pm/PackageUserState.java9
-rw-r--r--core/java/android/content/pm/ResolveInfo.java8
-rw-r--r--core/java/android/content/res/ColorStateList.java80
-rw-r--r--core/java/android/content/res/Configuration.java7
-rw-r--r--core/java/android/content/res/Resources.java172
-rw-r--r--core/java/android/database/DatabaseErrorHandler.java5
-rwxr-xr-xcore/java/android/database/DefaultDatabaseErrorHandler.java4
-rw-r--r--core/java/android/hardware/ICameraService.aidl7
-rw-r--r--core/java/android/hardware/SystemSensorManager.java27
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java131
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java20
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java3
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java126
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java10
-rw-r--r--core/java/android/hardware/camera2/params/OutputConfiguration.java30
-rw-r--r--core/java/android/hardware/camera2/params/StreamConfigurationMap.java224
-rw-r--r--core/java/android/hardware/fingerprint/Fingerprint.aidl (renamed from core/java/android/service/fingerprint/Fingerprint.aidl)2
-rw-r--r--core/java/android/hardware/fingerprint/Fingerprint.java (renamed from core/java/android/service/fingerprint/Fingerprint.java)2
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java (renamed from core/java/android/service/fingerprint/FingerprintManager.java)4
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintUtils.java (renamed from core/java/android/service/fingerprint/FingerprintUtils.java)2
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl (renamed from core/java/android/service/fingerprint/IFingerprintService.aidl)6
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl (renamed from core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl)2
-rw-r--r--core/java/android/hardware/hdmi/HdmiTvClient.java21
-rw-r--r--core/java/android/inputmethodservice/ExtractEditLayout.java2
-rw-r--r--core/java/android/net/ConnectivityManager.java33
-rw-r--r--core/java/android/net/NetworkPolicyManager.java27
-rw-r--r--core/java/android/net/NetworkStats.java41
-rw-r--r--core/java/android/net/TrafficStats.java2
-rw-r--r--core/java/android/net/Uri.java12
-rw-r--r--core/java/android/net/WifiKey.java3
-rw-r--r--core/java/android/os/BatteryStats.java88
-rw-r--r--core/java/android/os/Environment.java244
-rw-r--r--core/java/android/os/FileUtils.java17
-rw-r--r--core/java/android/os/Process.java6
-rw-r--r--core/java/android/os/storage/DiskInfo.aidl19
-rw-r--r--core/java/android/os/storage/DiskInfo.java127
-rw-r--r--core/java/android/os/storage/IMountService.java212
-rw-r--r--core/java/android/os/storage/IMountServiceListener.java47
-rw-r--r--core/java/android/os/storage/StorageEventListener.java5
-rw-r--r--core/java/android/os/storage/StorageManager.java484
-rw-r--r--core/java/android/os/storage/StorageVolume.java111
-rw-r--r--core/java/android/os/storage/VolumeInfo.aidl19
-rw-r--r--core/java/android/os/storage/VolumeInfo.java296
-rw-r--r--core/java/android/preference/Preference.java2
-rw-r--r--core/java/android/provider/AlarmClock.java144
-rw-r--r--core/java/android/provider/CallLog.java8
-rw-r--r--core/java/android/provider/ContactsContract.java93
-rw-r--r--core/java/android/provider/ContactsInternal.java112
-rw-r--r--core/java/android/provider/MediaStore.java29
-rw-r--r--core/java/android/provider/SearchIndexableData.java2
-rw-r--r--core/java/android/provider/SearchIndexableResource.java2
-rw-r--r--core/java/android/provider/SearchIndexablesContract.java4
-rw-r--r--core/java/android/provider/SearchIndexablesProvider.java2
-rw-r--r--core/java/android/provider/Settings.java140
-rw-r--r--core/java/android/provider/VoicemailContract.java4
-rw-r--r--core/java/android/security/IKeystoreService.aidl8
-rw-r--r--core/java/android/security/NetworkSecurityPolicy.java8
-rw-r--r--core/java/android/security/keymaster/KeymasterDefs.java56
-rw-r--r--core/java/android/security/keymaster/OperationResult.java3
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java6
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java16
-rw-r--r--core/java/android/service/voice/IVoiceInteractionSession.aidl4
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java6
-rw-r--r--core/java/android/service/voice/VoiceInteractionServiceInfo.java13
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java71
-rw-r--r--core/java/android/text/DynamicLayout.java32
-rw-r--r--core/java/android/text/Hyphenator.java87
-rw-r--r--core/java/android/text/Layout.java65
-rw-r--r--core/java/android/text/SpanSet.java13
-rw-r--r--core/java/android/text/StaticLayout.java183
-rw-r--r--core/java/android/text/TextLine.java4
-rw-r--r--core/java/android/text/format/Time.java1
-rw-r--r--core/java/android/transition/Fade.java23
-rw-r--r--core/java/android/util/DebugUtils.java57
-rw-r--r--core/java/android/util/MathUtils.java20
-rw-r--r--core/java/android/view/Choreographer.java79
-rw-r--r--core/java/android/view/DisplayListCanvas.java1
-rw-r--r--core/java/android/view/IGraphicsStats.aidl26
-rw-r--r--core/java/android/view/PhoneWindow.java109
-rw-r--r--core/java/android/view/RenderNode.java7
-rw-r--r--core/java/android/view/ScaleGestureDetector.java92
-rw-r--r--core/java/android/view/ThreadedRenderer.java42
-rw-r--r--core/java/android/view/View.java154
-rw-r--r--core/java/android/view/ViewAssistStructure.java46
-rw-r--r--core/java/android/view/ViewDebug.java38
-rw-r--r--core/java/android/view/ViewGroup.java31
-rw-r--r--core/java/android/view/ViewRootImpl.java96
-rw-r--r--core/java/android/view/Window.java4
-rw-r--r--core/java/android/view/WindowManager.java7
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java124
-rw-r--r--core/java/android/webkit/WebChromeClient.java8
-rw-r--r--core/java/android/webkit/WebView.java8
-rw-r--r--core/java/android/webkit/WebViewClient.java2
-rw-r--r--core/java/android/webkit/WebViewProvider.java3
-rw-r--r--core/java/android/widget/AbsListView.java13
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java261
-rw-r--r--core/java/android/widget/AnalogClock.java2
-rwxr-xr-xcore/java/android/widget/DatePickerCalendarDelegate.java31
-rw-r--r--core/java/android/widget/DayPickerAdapter.java107
-rw-r--r--core/java/android/widget/DayPickerView.java15
-rw-r--r--core/java/android/widget/Editor.java221
-rw-r--r--core/java/android/widget/GridView.java50
-rw-r--r--core/java/android/widget/ImageView.java11
-rw-r--r--core/java/android/widget/ListView.java29
-rw-r--r--core/java/android/widget/ProgressBar.java10
-rw-r--r--core/java/android/widget/RadialTimePickerView.java324
-rw-r--r--core/java/android/widget/RemoteViewsAdapter.java6
-rw-r--r--core/java/android/widget/SimpleMonthView.java411
-rw-r--r--core/java/android/widget/Switch.java4
-rw-r--r--core/java/android/widget/TextView.java120
-rw-r--r--core/java/android/widget/TimePicker.java109
-rw-r--r--core/java/android/widget/TimePickerClockDelegate.java150
-rw-r--r--core/java/android/widget/TimePickerSpinnerDelegate.java14
-rw-r--r--core/java/android/widget/YearPickerView.java31
159 files changed, 7051 insertions, 2158 deletions
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 6957435..480d171 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -203,6 +203,14 @@ public class AccountManager {
public static final String KEY_USERDATA = "userdata";
/**
+ * Bundle key used to supply the last time the credentials of the account
+ * were authenticated successfully. Time is specified in milliseconds since
+ * epoch.
+ */
+ public static final String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH =
+ "lastAuthenticatedTimeMillisEpoch";
+
+ /**
* Authenticators using 'customTokens' option will also get the UID of the
* caller
*/
@@ -663,6 +671,31 @@ public class AccountManager {
}
/**
+ * Informs the system that the account has been authenticated recently. This
+ * recency may be used by other applications to verify the account. This
+ * should be called only when the user has entered correct credentials for
+ * the account.
+ * <p>
+ * It is not safe to call this method from the main thread. As such, call it
+ * from another thread.
+ * <p>
+ * This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and should be
+ * called from the account's authenticator.
+ *
+ * @param account The {@link Account} to be updated.
+ */
+ public boolean accountAuthenticated(Account account) {
+ if (account == null)
+ throw new IllegalArgumentException("account is null");
+ try {
+ return mService.accountAuthenticated(account);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
* Rename the specified {@link Account}. This is equivalent to removing
* the existing account and adding a new renamed account with the old
* account's user data.
@@ -1544,15 +1577,20 @@ public class AccountManager {
* with these fields if activity or password was supplied and
* the account was successfully verified:
* <ul>
- * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+ * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account verified
* <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
* <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success
* </ul>
*
* If no activity or password was specified, the returned Bundle contains
- * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the
- * password prompt. If an error occurred,
- * {@link AccountManagerFuture#getResult()} throws:
+ * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+ * password prompt.
+ *
+ * <p>Also the returning Bundle may contain {@link
+ * #KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH} indicating the last time the
+ * credential was validated/created.
+ *
+ * If an error occurred,{@link AccountManagerFuture#getResult()} throws:
* <ul>
* <li> {@link AuthenticatorException} if the authenticator failed to respond
* <li> {@link OperationCanceledException} if the operation was canceled for
@@ -1625,9 +1663,9 @@ public class AccountManager {
* <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
* </ul>
*
- * If no activity was specified, the returned Bundle contains only
+ * If no activity was specified, the returned Bundle contains
* {@link #KEY_INTENT} with the {@link Intent} needed to launch the
- * password prompt. If an error occurred,
+ * password prompt. If an error occurred,
* {@link AccountManagerFuture#getResult()} throws:
* <ul>
* <li> {@link AuthenticatorException} if the authenticator failed to respond
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index aa41161..04b3c88 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -67,6 +67,7 @@ interface IAccountManager {
boolean expectActivityLaunch);
void confirmCredentialsAsUser(in IAccountManagerResponse response, in Account account,
in Bundle options, boolean expectActivityLaunch, int userId);
+ boolean accountAuthenticated(in Account account);
void getAuthTokenLabel(in IAccountManagerResponse response, String accountType,
String authTokenType);
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 021194c..81a01ee 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -947,6 +947,11 @@ public class AnimatorInflater {
Keyframe.ofInt(fraction);
}
+ final int resID = a.getResourceId(R.styleable.Keyframe_interpolator, 0);
+ if (resID > 0) {
+ final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
+ keyframe.setInterpolator(interpolator);
+ }
a.recycle();
return keyframe;
diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java
index 56da940..0173079 100644
--- a/core/java/android/animation/FloatKeyframeSet.java
+++ b/core/java/android/animation/FloatKeyframeSet.java
@@ -118,13 +118,14 @@ class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes {
FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
- if (interpolator != null) {
- fraction = interpolator.getInterpolation(fraction);
- }
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java
index 12a4bf9..73f9af1 100644
--- a/core/java/android/animation/IntKeyframeSet.java
+++ b/core/java/android/animation/IntKeyframeSet.java
@@ -117,13 +117,14 @@ class IntKeyframeSet extends KeyframeSet implements Keyframes.IntKeyframes {
IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
- if (interpolator != null) {
- fraction = interpolator.getInterpolation(fraction);
- }
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
return mEvaluator == null ?
prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index c80e162..32edd4d 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -201,7 +201,6 @@ class KeyframeSet implements Keyframes {
* @return The animated value.
*/
public Object getValue(float fraction) {
-
// Special-case optimization for the common case of only two keyframes
if (mNumKeyframes == 2) {
if (mInterpolator != null) {
@@ -238,12 +237,13 @@ class KeyframeSet implements Keyframes {
Keyframe nextKeyframe = mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
- if (interpolator != null) {
- fraction = interpolator.getInterpolation(fraction);
- }
final float prevFraction = prevKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) /
(nextKeyframe.getFraction() - prevFraction);
+ // Apply interpolator on the proportional duration.
+ if (interpolator != null) {
+ intervalFraction = interpolator.getInterpolation(intervalFraction);
+ }
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
nextKeyframe.getValue());
}
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 3f71d51..f933373 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -50,9 +50,11 @@ import java.lang.ref.WeakReference;
* value. Alternatively, you can leave the fractions off and the keyframes will be equally
* distributed within the total duration. Also, a keyframe with no value will derive its value
* from the target object when the animator starts, just like animators with only one
- * value specified.</p>
+ * value specified. In addition, an optional interpolator can be specified. The interpolator will
+ * be applied on the interval between the keyframe that the interpolator is set on and the previous
+ * keyframe. When no interpolator is supplied, the default linear interpolator will be used. </p>
*
- * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh_kf.xml KeyframeResources}
+ * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh_kf_interpolated.xml KeyframeResources}
*
* <div class="special reference">
* <h3>Developer Guides</h3>
diff --git a/core/java/android/animation/TimeAnimator.java b/core/java/android/animation/TimeAnimator.java
index 1738ade..1ba68df 100644
--- a/core/java/android/animation/TimeAnimator.java
+++ b/core/java/android/animation/TimeAnimator.java
@@ -51,6 +51,7 @@ public class TimeAnimator extends ValueAnimator {
public void setCurrentPlayTime(long playTime) {
long currentTime = AnimationUtils.currentAnimationTimeMillis();
mStartTime = Math.max(mStartTime, currentTime - playTime);
+ mStartTimeCommitted = true; // do not allow start time to be compensated for jank
animationFrame(currentTime);
}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 85dc832..2386007 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -20,6 +20,7 @@ import android.annotation.CallSuper;
import android.os.Looper;
import android.os.Trace;
import android.util.AndroidRuntimeException;
+import android.util.Log;
import android.view.Choreographer;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AnimationUtils;
@@ -64,6 +65,8 @@ import java.util.HashMap;
*/
@SuppressWarnings("unchecked")
public class ValueAnimator extends Animator {
+ private static final String TAG = "ValueAnimator";
+ private static final boolean DEBUG = false;
/**
* Internal constants
@@ -85,12 +88,30 @@ public class ValueAnimator extends Animator {
* to clone() to make deep copies of them.
*/
- // The first time that the animation's animateFrame() method is called. This time is used to
- // determine elapsed time (and therefore the elapsed fraction) in subsequent calls
- // to animateFrame()
+ /**
+ * The first time that the animation's animateFrame() method is called. This time is used to
+ * determine elapsed time (and therefore the elapsed fraction) in subsequent calls
+ * to animateFrame().
+ *
+ * Whenever mStartTime is set, you must also update mStartTimeCommitted.
+ */
long mStartTime;
/**
+ * When true, the start time has been firmly committed as a chosen reference point in
+ * time by which the progress of the animation will be evaluated. When false, the
+ * start time may be updated when the first animation frame is committed so as
+ * to compensate for jank that may have occurred between when the start time was
+ * initialized and when the frame was actually drawn.
+ *
+ * This flag is generally set to false during the first frame of the animation
+ * when the animation playing state transitions from STOPPED to RUNNING or
+ * resumes after having been paused. This flag is set to true when the start time
+ * is firmly committed and should not be further compensated for jank.
+ */
+ boolean mStartTimeCommitted;
+
+ /**
* Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked
* to a value.
*/
@@ -528,6 +549,7 @@ public class ValueAnimator extends Animator {
* value makes it easier to compose statements together that construct and then set the
* duration, as in <code>ValueAnimator.ofInt(0, 10).setDuration(500).start()</code>.
*/
+ @Override
public ValueAnimator setDuration(long duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " +
@@ -547,6 +569,7 @@ public class ValueAnimator extends Animator {
*
* @return The length of the animation, in milliseconds.
*/
+ @Override
public long getDuration() {
return mUnscaledDuration;
}
@@ -608,6 +631,7 @@ public class ValueAnimator extends Animator {
long seekTime = (long) (mDuration * fraction);
long currentTime = AnimationUtils.currentAnimationTimeMillis();
mStartTime = currentTime - seekTime;
+ mStartTimeCommitted = true; // do not allow start time to be compensated for jank
if (mPlayingState != RUNNING) {
mSeekFraction = fraction;
mPlayingState = SEEKED;
@@ -644,7 +668,7 @@ public class ValueAnimator extends Animator {
* @hide
*/
@SuppressWarnings("unchecked")
- protected static class AnimationHandler implements Runnable {
+ protected static class AnimationHandler {
// The per-thread list of all active animations
/** @hide */
protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
@@ -667,6 +691,7 @@ public class ValueAnimator extends Animator {
private final Choreographer mChoreographer;
private boolean mAnimationScheduled;
+ private long mLastFrameTime;
private AnimationHandler() {
mChoreographer = Choreographer.getInstance();
@@ -679,7 +704,9 @@ public class ValueAnimator extends Animator {
scheduleAnimation();
}
- private void doAnimationFrame(long frameTime) {
+ void doAnimationFrame(long frameTime) {
+ mLastFrameTime = frameTime;
+
// mPendingAnimations holds any animations that have requested to be started
// We're going to clear mPendingAnimations, but starting animation may
// cause more to be added to the pending list (for example, if one animation
@@ -700,6 +727,7 @@ public class ValueAnimator extends Animator {
}
}
}
+
// Next, process animations currently sitting on the delayed queue, adding
// them to the active animations if they are ready
int numDelayedAnims = mDelayedAnims.size();
@@ -740,6 +768,9 @@ public class ValueAnimator extends Animator {
mEndingAnims.clear();
}
+ // Schedule final commit for the frame.
+ mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null);
+
// If there are still active or delayed animations, schedule a future call to
// onAnimate to process the next frame of the animations.
if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
@@ -747,19 +778,37 @@ public class ValueAnimator extends Animator {
}
}
- // Called by the Choreographer.
- @Override
- public void run() {
- mAnimationScheduled = false;
- doAnimationFrame(mChoreographer.getFrameTime());
+ void commitAnimationFrame(long frameTime) {
+ final long adjustment = frameTime - mLastFrameTime;
+ final int numAnims = mAnimations.size();
+ for (int i = 0; i < numAnims; ++i) {
+ mAnimations.get(i).commitAnimationFrame(adjustment);
+ }
}
private void scheduleAnimation() {
if (!mAnimationScheduled) {
- mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);
+ mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
mAnimationScheduled = true;
}
}
+
+ // Called by the Choreographer.
+ final Runnable mAnimate = new Runnable() {
+ @Override
+ public void run() {
+ mAnimationScheduled = false;
+ doAnimationFrame(mChoreographer.getFrameTime());
+ }
+ };
+
+ // Called by the Choreographer.
+ final Runnable mCommit = new Runnable() {
+ @Override
+ public void run() {
+ commitAnimationFrame(mChoreographer.getFrameTime());
+ }
+ };
}
/**
@@ -768,6 +817,7 @@ public class ValueAnimator extends Animator {
*
* @return the number of milliseconds to delay running the animation
*/
+ @Override
public long getStartDelay() {
return mUnscaledStartDelay;
}
@@ -778,6 +828,7 @@ public class ValueAnimator extends Animator {
* @param startDelay The amount of the delay, in milliseconds
*/
+ @Override
public void setStartDelay(long startDelay) {
this.mStartDelay = (long)(startDelay * sDurationScale);
mUnscaledStartDelay = startDelay;
@@ -1148,6 +1199,7 @@ public class ValueAnimator extends Animator {
long currentPlayTime = currentTime - mStartTime;
long timeLeft = mDuration - currentPlayTime;
mStartTime = currentTime - timeLeft;
+ mStartTimeCommitted = true; // do not allow start time to be compensated for jank
mReversing = !mReversing;
} else if (mStarted) {
end();
@@ -1254,9 +1306,9 @@ public class ValueAnimator extends Animator {
}
long deltaTime = currentTime - mDelayStartTime;
if (deltaTime > mStartDelay) {
- // startDelay ended - start the anim and record the
- // mStartTime appropriately
- mStartTime = currentTime - (deltaTime - mStartDelay);
+ // startDelay ended - start the anim and record the mStartTime appropriately
+ mStartTime = mDelayStartTime + mStartDelay;
+ mStartTimeCommitted = true; // do not allow start time to be compensated for jank
mPlayingState = RUNNING;
return true;
}
@@ -1264,6 +1316,22 @@ public class ValueAnimator extends Animator {
}
/**
+ * Applies an adjustment to the animation to compensate for jank between when
+ * the animation first ran and when the frame was drawn.
+ */
+ void commitAnimationFrame(long adjustment) {
+ if (!mStartTimeCommitted) {
+ mStartTimeCommitted = true;
+ if (mPlayingState == RUNNING && adjustment > 0) {
+ mStartTime += adjustment;
+ if (DEBUG) {
+ Log.d(TAG, "Adjusted start time by " + adjustment + " ms: " + toString());
+ }
+ }
+ }
+ }
+
+ /**
* This internal function processes a single animation frame for a given animation. The
* currentTime parameter is the timing pulse sent by the handler, used to calculate the
* elapsed duration, and therefore
@@ -1303,6 +1371,8 @@ public class ValueAnimator extends Animator {
mCurrentIteration += (int) fraction;
fraction = fraction % 1f;
mStartTime += mDuration;
+ // Note: We do not need to update the value of mStartTimeCommitted here
+ // since we just added a duration offset.
} else {
done = true;
fraction = Math.min(fraction, 1.0f);
@@ -1334,6 +1404,7 @@ public class ValueAnimator extends Animator {
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
}
+ mStartTimeCommitted = false; // allow start time to be compensated for jank
}
if (mPaused) {
if (mPauseTime < 0) {
@@ -1345,6 +1416,7 @@ public class ValueAnimator extends Animator {
if (mPauseTime > 0) {
// Offset by the duration that the animation was paused
mStartTime += (frameTime - mPauseTime);
+ mStartTimeCommitted = false; // allow start time to be compensated for jank
}
}
// The frame time might be before the start time during the first frame of
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index d143f8b..8f125d7 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2682,6 +2682,47 @@ public class ActivityManager {
}
/**
+ * Request that the system start watching for the calling process to exceed a pss
+ * size as given here. Once called, the system will look for any occassions where it
+ * sees the associated process with a larger pss size and, when this happens, automatically
+ * pull a heap dump from it and allow the user to share the data. Note that this request
+ * continues running even if the process is killed and restarted. To remove the watch,
+ * use {@link #clearWatchHeapLimit()}.
+ *
+ * <p>This API only work if running on a debuggable (userdebug or eng) build.</p>
+ *
+ * <p>Callers can optionally implement {@link #ACTION_REPORT_HEAP_LIMIT} to directly
+ * handle heap limit reports themselves.</p>
+ *
+ * @param pssSize The size in bytes to set the limit at.
+ */
+ public void setWatchHeapLimit(long pssSize) {
+ try {
+ ActivityManagerNative.getDefault().setDumpHeapDebugLimit(null, 0, pssSize,
+ mContext.getPackageName());
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Action an app can implement to handle reports from {@link #setWatchHeapLimit(long)}.
+ * If your package has an activity handling this action, it will be launched with the
+ * heap data provided to it the same way as {@link Intent#ACTION_SEND}. Note that to
+ * match the activty must support this action and a MIME type of "*&#47;*".
+ */
+ public static final String ACTION_REPORT_HEAP_LIMIT = "android.app.action.REPORT_HEAP_LIMIT";
+
+ /**
+ * Clear a heap watch limit previously set by {@link #setWatchHeapLimit(long)}.
+ */
+ public void clearWatchHeapLimit() {
+ try {
+ ActivityManagerNative.getDefault().setDumpHeapDebugLimit(null, 0, 0, null);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* @hide
*/
public void startLockTaskMode(int taskId) {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 1484af8..f63d13c 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2427,8 +2427,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String procName = data.readString();
+ int uid = data.readInt();
long maxMemSize = data.readLong();
- setDumpHeapDebugLimit(procName, maxMemSize);
+ String reportPackage = data.readString();
+ setDumpHeapDebugLimit(procName, uid, maxMemSize, reportPackage);
reply.writeNoException();
return true;
}
@@ -2450,6 +2452,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
+
+ case UPDATE_LOCK_TASK_PACKAGES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int userId = data.readInt();
+ String[] packages = data.readStringArray();
+ updateLockTaskPackages(userId, packages);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -5644,12 +5655,15 @@ class ActivityManagerProxy implements IActivityManager
}
@Override
- public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException {
+ public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize,
+ String reportPackage) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeString(processName);
+ data.writeInt(uid);
data.writeLong(maxMemSize);
+ data.writeString(reportPackage);
mRemote.transact(SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
@@ -5682,5 +5696,18 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
+ @Override
+ public void updateLockTaskPackages(int userId, String[] packages) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(userId);
+ data.writeStringArray(packages);
+ mRemote.transact(UPDATE_LOCK_TASK_PACKAGES_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4880db1..ed05321 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -94,7 +94,7 @@ import android.view.ViewRootImpl;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
-import android.renderscript.RenderScript;
+import android.renderscript.RenderScriptCacheDir;
import android.security.AndroidKeyStoreProvider;
import com.android.internal.app.IVoiceInteractor;
@@ -254,18 +254,21 @@ public final class ActivityThread {
}
}
+ static final class AcquiringProviderRecord {
+ IActivityManager.ContentProviderHolder holder;
+ boolean acquiring = true;
+ int requests = 1;
+ }
+
// The lock of mProviderMap protects the following variables.
- final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
- = new ArrayMap<ProviderKey, ProviderClientRecord>();
- final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap
- = new ArrayMap<IBinder, ProviderRefCount>();
- final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders
- = new ArrayMap<IBinder, ProviderClientRecord>();
- final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName
- = new ArrayMap<ComponentName, ProviderClientRecord>();
+ final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap = new ArrayMap<>();
+ final ArrayMap<ProviderKey, AcquiringProviderRecord> mAcquiringProviderMap = new ArrayMap<>();
+ final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap = new ArrayMap<>();
+ final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders = new ArrayMap<>();
+ final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName = new ArrayMap<>();
final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners
- = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>();
+ = new ArrayMap<>();
final GcIdler mGcIdler = new GcIdler();
boolean mGcIdlerScheduled = false;
@@ -345,7 +348,7 @@ public final class ActivityThread {
}
}
- final class ProviderClientRecord {
+ static final class ProviderClientRecord {
final String[] mNames;
final IContentProvider mProvider;
final ContentProvider mLocalProvider;
@@ -3211,7 +3214,7 @@ public final class ActivityThread {
if (cv == null) {
mThumbnailCanvas = cv = new Canvas();
}
-
+
cv.setBitmap(thumbnail);
if (!r.activity.onCreateThumbnail(thumbnail, cv)) {
mAvailThumbnailBitmap = thumbnail;
@@ -3513,12 +3516,12 @@ public final class ActivityThread {
private void handleWindowVisibility(IBinder token, boolean show) {
ActivityClientRecord r = mActivities.get(token);
-
+
if (r == null) {
Log.w(TAG, "handleWindowVisibility: no activity for token " + token);
return;
}
-
+
if (!show && !r.stopped) {
performStopActivityInner(r, null, show, false);
} else if (show && r.stopped) {
@@ -3950,10 +3953,10 @@ public final class ActivityThread {
}
}
}
-
+
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
+ tmp.token + ": changedConfig=" + changedConfig);
-
+
// If there was a pending configuration change, execute it first.
if (changedConfig != null) {
mCurDefaultDisplayDpi = changedConfig.densityDpi;
@@ -4151,7 +4154,7 @@ public final class ActivityThread {
if (config == null) {
return;
}
-
+
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
+ config);
@@ -4289,7 +4292,7 @@ public final class ActivityThread {
ApplicationPackageManager.handlePackageBroadcast(cmd, packages,
hasPkgInfo);
}
-
+
final void handleLowMemory() {
ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);
@@ -4336,10 +4339,10 @@ public final class ActivityThread {
String[] packages = getPackageManager().getPackagesForUid(uid);
// If there are several packages in this application we won't
- // initialize the graphics disk caches
+ // initialize the graphics disk caches
if (packages != null && packages.length == 1) {
HardwareRenderer.setupDiskCache(cacheDir);
- RenderScript.setupDiskCache(cacheDir);
+ RenderScriptCacheDir.setupDiskCache(cacheDir);
}
} catch (RemoteException e) {
// Ignore
@@ -4693,22 +4696,57 @@ public final class ActivityThread {
public final IContentProvider acquireProvider(
Context c, String auth, int userId, boolean stable) {
- final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
+ final ProviderKey key = new ProviderKey(auth, userId);
+ final IContentProvider provider = acquireExistingProvider(c, key, stable);
if (provider != null) {
return provider;
}
+ AcquiringProviderRecord r;
+ boolean first = false;
+ synchronized (mAcquiringProviderMap) {
+ r = mAcquiringProviderMap.get(key);
+ if (r == null) {
+ r = new AcquiringProviderRecord();
+ mAcquiringProviderMap.put(key, r);
+ first = true;
+ } else {
+ r.requests++;
+ }
+ }
- // There is a possible race here. Another thread may try to acquire
- // the same provider at the same time. When this happens, we want to ensure
- // that the first one wins.
- // Note that we cannot hold the lock while acquiring and installing the
- // provider since it might take a long time to run and it could also potentially
- // be re-entrant in the case where the provider is in the same process.
IActivityManager.ContentProviderHolder holder = null;
- try {
- holder = ActivityManagerNative.getDefault().getContentProvider(
- getApplicationThread(), auth, userId, stable);
- } catch (RemoteException ex) {
+ if (first) {
+ // Multiple threads may try to acquire the same provider at the same time.
+ // When this happens, we only let the first one really gets provider.
+ // Other threads just wait for its result.
+ // Note that we cannot hold the lock while acquiring and installing the
+ // provider since it might take a long time to run and it could also potentially
+ // be re-entrant in the case where the provider is in the same process.
+ try {
+ holder = ActivityManagerNative.getDefault().getContentProvider(
+ getApplicationThread(), auth, userId, stable);
+ } catch (RemoteException ex) {
+ }
+ synchronized (r) {
+ r.holder = holder;
+ r.acquiring = false;
+ r.notifyAll();
+ }
+ } else {
+ synchronized (r) {
+ while (r.acquiring) {
+ try {
+ r.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ holder = r.holder;
+ }
+ }
+ synchronized (mAcquiringProviderMap) {
+ if (--r.requests == 0) {
+ mAcquiringProviderMap.remove(key);
+ }
}
if (holder == null) {
Slog.e(TAG, "Failed to find provider info for " + auth);
@@ -4792,8 +4830,12 @@ public final class ActivityThread {
public final IContentProvider acquireExistingProvider(
Context c, String auth, int userId, boolean stable) {
+ return acquireExistingProvider(c, new ProviderKey(auth, userId), stable);
+ }
+
+ final IContentProvider acquireExistingProvider(
+ Context c, ProviderKey key, boolean stable) {
synchronized (mProviderMap) {
- final ProviderKey key = new ProviderKey(auth, userId);
final ProviderClientRecord pr = mProviderMap.get(key);
if (pr == null) {
return null;
@@ -4804,7 +4846,7 @@ public final class ActivityThread {
if (!jBinder.isBinderAlive()) {
// The hosting process of the provider has died; we can't
// use this one.
- Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
+ Log.i(TAG, "Acquiring provider " + key.authority + " for user " + key.userId
+ ": existing object's process dead");
handleUnstableProviderDiedLocked(jBinder, true);
return null;
@@ -5126,18 +5168,12 @@ public final class ActivityThread {
if (DEBUG_PROVIDER) {
Slog.v(TAG, "installProvider: lost the race, updating ref count");
}
- // We need to transfer our new reference to the existing
- // ref count, releasing the old one... but only if
- // release is needed (that is, it is not running in the
- // system process).
+ // The provider has already been installed, so we need
+ // to increase reference count to the existing one, but
+ // only if release is needed (that is, it is not running
+ // in the system process or local to the process).
if (!noReleaseNeeded) {
incProviderRefLocked(prc, stable);
- try {
- ActivityManagerNative.getDefault().removeContentProvider(
- holder.connection, stable);
- } catch (RemoteException e) {
- //do nothing content provider object is dead any way
- }
}
} else {
ProviderClientRecord client = installProviderAuthoritiesLocked(
@@ -5231,7 +5267,7 @@ public final class ActivityThread {
if (mPendingConfiguration == null ||
mPendingConfiguration.isOtherSeqNewer(newConfig)) {
mPendingConfiguration = newConfig;
-
+
sendMessage(H.CONFIGURATION_CHANGED, newConfig);
}
}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4bd2332..381c20c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -208,8 +208,12 @@ public class AppOpsManager {
public static final int OP_ACTIVATE_VPN = 47;
/** @hide Access the WallpaperManagerAPI to write wallpapers. */
public static final int OP_WRITE_WALLPAPER = 48;
+ /** @hide Received the assist structure from an app. */
+ public static final int OP_ASSIST_STRUCTURE = 49;
+ /** @hide Received a screenshot from assist. */
+ public static final int OP_ASSIST_SCREENSHOT = 50;
/** @hide */
- public static final int _NUM_OP = 49;
+ public static final int _NUM_OP = 51;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION =
@@ -288,6 +292,8 @@ public class AppOpsManager {
OP_PROJECT_MEDIA,
OP_ACTIVATE_VPN,
OP_WRITE_WALLPAPER,
+ OP_ASSIST_STRUCTURE,
+ OP_ASSIST_SCREENSHOT,
};
/**
@@ -344,6 +350,8 @@ public class AppOpsManager {
null,
OPSTR_ACTIVATE_VPN,
null,
+ null,
+ null,
};
/**
@@ -400,6 +408,8 @@ public class AppOpsManager {
"PROJECT_MEDIA",
"ACTIVATE_VPN",
"WRITE_WALLPAPER",
+ "ASSIST_STRUCTURE",
+ "ASSIST_SCREENSHOT"
};
/**
@@ -456,6 +466,8 @@ public class AppOpsManager {
null, // no permission for projecting media
null, // no permission for activating vpn
null, // no permission for supporting wallpaper
+ null, // no permission for receiving assist structure
+ null, // no permission for receiving assist screenshot
};
/**
@@ -513,6 +525,8 @@ public class AppOpsManager {
null, //PROJECT_MEDIA
UserManager.DISALLOW_CONFIG_VPN, // ACTIVATE_VPN
UserManager.DISALLOW_WALLPAPER, // WRITE_WALLPAPER
+ null, // ASSIST_STRUCTURE
+ null, // ASSIST_SCREENSHOT
};
/**
@@ -569,6 +583,8 @@ public class AppOpsManager {
false, //PROJECT_MEDIA
false, //ACTIVATE_VPN
false, //WALLPAPER
+ false, //ASSIST_STRUCTURE
+ false, //ASSIST_SCREENSHOT
};
/**
@@ -624,6 +640,8 @@ public class AppOpsManager {
AppOpsManager.MODE_IGNORED, // OP_PROJECT_MEDIA
AppOpsManager.MODE_IGNORED, // OP_ACTIVATE_VPN
AppOpsManager.MODE_ALLOWED,
+ AppOpsManager.MODE_ALLOWED,
+ AppOpsManager.MODE_ALLOWED,
};
/**
@@ -683,6 +701,8 @@ public class AppOpsManager {
false,
false,
false,
+ false,
+ false,
};
private static HashMap<String, Integer> sOpStrToOp = new HashMap<String, Integer>();
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index 6c2511e..8692336 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -235,10 +235,13 @@ public class ApplicationErrorReport implements Parcelable {
dest.writeString(processName);
dest.writeLong(time);
dest.writeInt(systemApp ? 1 : 0);
+ dest.writeInt(crashInfo != null ? 1 : 0);
switch (type) {
case TYPE_CRASH:
- crashInfo.writeToParcel(dest, flags);
+ if (crashInfo != null) {
+ crashInfo.writeToParcel(dest, flags);
+ }
break;
case TYPE_ANR:
anrInfo.writeToParcel(dest, flags);
@@ -259,10 +262,11 @@ public class ApplicationErrorReport implements Parcelable {
processName = in.readString();
time = in.readLong();
systemApp = in.readInt() == 1;
+ boolean hasCrashInfo = in.readInt() == 1;
switch (type) {
case TYPE_CRASH:
- crashInfo = new CrashInfo(in);
+ crashInfo = hasCrashInfo ? new CrashInfo(in) : null;
anrInfo = null;
batteryInfo = null;
runningServiceInfo = null;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 6d74905..ffdc81d 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -36,6 +36,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.InstrumentationInfo;
+import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
import android.content.pm.ManifestDigest;
import android.content.pm.PackageInfo;
@@ -61,19 +62,19 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
import android.util.Log;
import android.view.Display;
-import android.os.SystemProperties;
+
+import dalvik.system.VMRuntime;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.internal.util.UserIcons;
-import dalvik.system.VMRuntime;
-
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -1309,6 +1310,55 @@ final class ApplicationPackageManager extends PackageManager {
}
@Override
+ public void verifyIntentFilter(int id, int verificationCode, List<String> outFailedDomains) {
+ try {
+ mPM.verifyIntentFilter(id, verificationCode, outFailedDomains);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
+ }
+
+ @Override
+ public int getIntentVerificationStatus(String packageName, int userId) {
+ try {
+ return mPM.getIntentVerificationStatus(packageName, userId);
+ } catch (RemoteException e) {
+ // Should never happen!
+ return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+ }
+
+ @Override
+ public boolean updateIntentVerificationStatus(String packageName, int status, int userId) {
+ try {
+ return mPM.updateIntentVerificationStatus(packageName, status, userId);
+ } catch (RemoteException e) {
+ // Should never happen!
+ return false;
+ }
+ }
+
+ @Override
+ public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) {
+ try {
+ return mPM.getIntentFilterVerifications(packageName);
+ } catch (RemoteException e) {
+ // Should never happen!
+ return null;
+ }
+ }
+
+ @Override
+ public List<IntentFilter> getAllIntentFilters(String packageName) {
+ try {
+ return mPM.getAllIntentFilters(packageName);
+ } catch (RemoteException e) {
+ // Should never happen!
+ return null;
+ }
+ }
+
+ @Override
public void setInstallerPackageName(String targetPackage,
String installerPackageName) {
try {
@@ -1323,7 +1373,17 @@ final class ApplicationPackageManager extends PackageManager {
try {
mPM.movePackage(packageName, observer, flags);
} catch (RemoteException e) {
- // Should never happen!
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
+ public void movePackageAndData(String packageName, String volumeUuid,
+ IPackageMoveObserver observer) {
+ try {
+ mPM.movePackageAndData(packageName, volumeUuid, observer);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
diff --git a/core/java/android/app/AssistStructure.java b/core/java/android/app/AssistStructure.java
index c435ccb..1e159a3 100644
--- a/core/java/android/app/AssistStructure.java
+++ b/core/java/android/app/AssistStructure.java
@@ -17,24 +17,25 @@
package android.app;
import android.content.ComponentName;
-import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
+import android.os.Binder;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PooledStringReader;
import android.os.PooledStringWriter;
+import android.os.RemoteException;
+import android.os.SystemClock;
import android.text.TextPaint;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.ViewAssistStructure;
-import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
-import android.widget.Checkable;
import java.util.ArrayList;
@@ -52,111 +53,49 @@ final public class AssistStructure implements Parcelable {
*/
public static final String ASSIST_KEY = "android:assist_structure";
- final ComponentName mActivityComponent;
+ boolean mHaveData;
+
+ ComponentName mActivityComponent;
final ArrayList<WindowNode> mWindowNodes = new ArrayList<>();
- ViewAssistStructureImpl mTmpViewAssistStructureImpl = new ViewAssistStructureImpl();
- Bundle mTmpExtras = new Bundle();
+ final ArrayList<ViewNodeBuilder> mPendingAsyncChildren = new ArrayList<>();
- final static class ViewAssistStructureImpl extends ViewAssistStructure {
- CharSequence mText;
- int mTextSelectionStart = -1;
- int mTextSelectionEnd = -1;
- int mTextColor = ViewNode.TEXT_COLOR_UNDEFINED;
- int mTextBackgroundColor = ViewNode.TEXT_COLOR_UNDEFINED;
- float mTextSize = 0;
- int mTextStyle = 0;
- CharSequence mHint;
+ SendChannel mSendChannel;
+ IBinder mReceiveChannel;
- @Override
- public void setText(CharSequence text) {
- mText = text;
- mTextSelectionStart = mTextSelectionEnd = -1;
- }
+ Rect mTmpRect = new Rect();
- @Override
- public void setText(CharSequence text, int selectionStart, int selectionEnd) {
- mText = text;
- mTextSelectionStart = selectionStart;
- mTextSelectionEnd = selectionEnd;
- }
+ static final int TRANSACTION_XFER = Binder.FIRST_CALL_TRANSACTION+1;
+ static final String DESCRIPTOR = "android.app.AssistStructure";
- @Override
- public void setTextPaint(TextPaint paint) {
- mTextColor = paint.getColor();
- mTextBackgroundColor = paint.bgColor;
- mTextSize = paint.getTextSize();
- mTextStyle = 0;
- Typeface tf = paint.getTypeface();
- if (tf != null) {
- if (tf.isBold()) {
- mTextStyle |= ViewNode.TEXT_STYLE_BOLD;
- }
- if (tf.isItalic()) {
- mTextStyle |= ViewNode.TEXT_STYLE_ITALIC;
- }
- }
- int pflags = paint.getFlags();
- if ((pflags& Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
- mTextStyle |= ViewNode.TEXT_STYLE_BOLD;
- }
- if ((pflags& Paint.UNDERLINE_TEXT_FLAG) != 0) {
- mTextStyle |= ViewNode.TEXT_STYLE_UNDERLINE;
- }
- if ((pflags& Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
- mTextStyle |= ViewNode.TEXT_STYLE_STRIKE_THRU;
+ final class SendChannel extends Binder {
+ @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ if (code == TRANSACTION_XFER) {
+ data.enforceInterface(DESCRIPTOR);
+ writeContentToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ return true;
+ } else {
+ return super.onTransact(code, data, reply, flags);
}
}
-
- @Override
- public void setHint(CharSequence hint) {
- mHint = hint;
- }
-
- @Override
- public CharSequence getText() {
- return mText;
- }
-
- @Override
- public int getTextSelectionStart() {
- return mTextSelectionStart;
- }
-
- @Override
- public int getTextSelectionEnd() {
- return mTextSelectionEnd;
- }
-
- @Override
- public CharSequence getHint() {
- return mHint;
- }
}
- final static class ViewNodeTextImpl {
- final CharSequence mText;
- final int mTextSelectionStart;
- final int mTextSelectionEnd;
+ final static class ViewNodeText {
+ CharSequence mText;
+ int mTextSelectionStart;
+ int mTextSelectionEnd;
int mTextColor;
int mTextBackgroundColor;
float mTextSize;
int mTextStyle;
- final String mHint;
+ String mHint;
- ViewNodeTextImpl(ViewAssistStructureImpl data) {
- mText = data.mText;
- mTextSelectionStart = data.mTextSelectionStart;
- mTextSelectionEnd = data.mTextSelectionEnd;
- mTextColor = data.mTextColor;
- mTextBackgroundColor = data.mTextBackgroundColor;
- mTextSize = data.mTextSize;
- mTextStyle = data.mTextStyle;
- mHint = data.mHint != null ? data.mHint.toString() : null;
+ ViewNodeText() {
}
- ViewNodeTextImpl(Parcel in) {
+ ViewNodeText(Parcel in) {
mText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mTextSelectionStart = in.readInt();
mTextSelectionEnd = in.readInt();
@@ -199,7 +138,9 @@ final public class AssistStructure implements Parcelable {
mWidth = rect.width();
mHeight = rect.height();
mTitle = root.getTitle();
- mRoot = new ViewNode(assist, view);
+ mRoot = new ViewNode();
+ ViewNodeBuilder builder = new ViewNodeBuilder(assist, mRoot, false);
+ view.dispatchProvideAssistStructure(builder);
}
WindowNode(Parcel in, PooledStringReader preader) {
@@ -260,16 +201,16 @@ final public class AssistStructure implements Parcelable {
public static final int TEXT_STYLE_UNDERLINE = 1<<2;
public static final int TEXT_STYLE_STRIKE_THRU = 1<<3;
- final int mId;
- final String mIdPackage;
- final String mIdType;
- final String mIdEntry;
- final int mX;
- final int mY;
- final int mScrollX;
- final int mScrollY;
- final int mWidth;
- final int mHeight;
+ int mId;
+ String mIdPackage;
+ String mIdType;
+ String mIdEntry;
+ int mX;
+ int mY;
+ int mScrollX;
+ int mScrollY;
+ int mWidth;
+ int mHeight;
static final int FLAGS_DISABLED = 0x00000001;
static final int FLAGS_VISIBILITY_MASK = View.VISIBLE|View.INVISIBLE|View.GONE;
@@ -283,104 +224,17 @@ final public class AssistStructure implements Parcelable {
static final int FLAGS_CLICKABLE = 0x00004000;
static final int FLAGS_LONG_CLICKABLE = 0x00200000;
- final int mFlags;
+ int mFlags;
- final String mClassName;
- final CharSequence mContentDescription;
+ String mClassName;
+ CharSequence mContentDescription;
- final ViewNodeTextImpl mText;
- final Bundle mExtras;
+ ViewNodeText mText;
+ Bundle mExtras;
- final ViewNode[] mChildren;
+ ViewNode[] mChildren;
- ViewNode(AssistStructure assistStructure, View view) {
- mId = view.getId();
- if (mId > 0 && (mId&0xff000000) != 0 && (mId&0x00ff0000) != 0
- && (mId&0x0000ffff) != 0) {
- String pkg, type, entry;
- try {
- Resources res = view.getResources();
- entry = res.getResourceEntryName(mId);
- type = res.getResourceTypeName(mId);
- pkg = res.getResourcePackageName(mId);
- } catch (Resources.NotFoundException e) {
- entry = type = pkg = null;
- }
- mIdPackage = pkg;
- mIdType = type;
- mIdEntry = entry;
- } else {
- mIdPackage = mIdType = mIdEntry = null;
- }
- mX = view.getLeft();
- mY = view.getTop();
- mScrollX = view.getScrollX();
- mScrollY = view.getScrollY();
- mWidth = view.getWidth();
- mHeight = view.getHeight();
- int flags = view.getVisibility();
- if (!view.isEnabled()) {
- flags |= FLAGS_DISABLED;
- }
- if (!view.isClickable()) {
- flags |= FLAGS_CLICKABLE;
- }
- if (!view.isFocusable()) {
- flags |= FLAGS_FOCUSABLE;
- }
- if (!view.isFocused()) {
- flags |= FLAGS_FOCUSED;
- }
- if (!view.isAccessibilityFocused()) {
- flags |= FLAGS_ACCESSIBILITY_FOCUSED;
- }
- if (!view.isSelected()) {
- flags |= FLAGS_SELECTED;
- }
- if (!view.isActivated()) {
- flags |= FLAGS_ACTIVATED;
- }
- if (!view.isLongClickable()) {
- flags |= FLAGS_LONG_CLICKABLE;
- }
- if (view instanceof Checkable) {
- flags |= FLAGS_CHECKABLE;
- if (((Checkable)view).isChecked()) {
- flags |= FLAGS_CHECKED;
- }
- }
- mFlags = flags;
- mClassName = view.getAccessibilityClassName().toString();
- mContentDescription = view.getContentDescription();
- final ViewAssistStructureImpl viewData = assistStructure.mTmpViewAssistStructureImpl;
- final Bundle extras = assistStructure.mTmpExtras;
- view.onProvideAssistStructure(viewData, extras);
- if (viewData.mText != null || viewData.mHint != null) {
- mText = new ViewNodeTextImpl(viewData);
- assistStructure.mTmpViewAssistStructureImpl = new ViewAssistStructureImpl();
- } else {
- mText = null;
- }
- if (!extras.isEmpty()) {
- mExtras = extras;
- assistStructure.mTmpExtras = new Bundle();
- } else {
- mExtras = null;
- }
- if (view instanceof ViewGroup) {
- ViewGroup vg = (ViewGroup)view;
- final int NCHILDREN = vg.getChildCount();
- if (NCHILDREN > 0) {
- mChildren = new ViewNode[NCHILDREN];
- for (int i=0; i<NCHILDREN; i++) {
- mChildren[i] = new ViewNode(assistStructure, vg.getChildAt(i));
- }
- } else {
- mChildren = null;
- }
- } else {
- mChildren = null;
- }
+ ViewNode() {
}
ViewNode(Parcel in, PooledStringReader preader) {
@@ -406,7 +260,7 @@ final public class AssistStructure implements Parcelable {
mClassName = preader.readString();
mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
if (in.readInt() != 0) {
- mText = new ViewNodeTextImpl(in);
+ mText = new ViewNodeText(in);
} else {
mText = null;
}
@@ -595,7 +449,250 @@ final public class AssistStructure implements Parcelable {
}
}
+ static class ViewNodeBuilder extends ViewAssistStructure {
+ final AssistStructure mAssist;
+ final ViewNode mNode;
+ final boolean mAsync;
+
+ ViewNodeBuilder(AssistStructure assist, ViewNode node, boolean async) {
+ mAssist = assist;
+ mNode = node;
+ mAsync = async;
+ }
+
+ @Override
+ public void setId(int id, String packageName, String typeName, String entryName) {
+ mNode.mId = id;
+ mNode.mIdPackage = packageName;
+ mNode.mIdType = typeName;
+ mNode.mIdEntry = entryName;
+ }
+
+ @Override
+ public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {
+ mNode.mX = left;
+ mNode.mY = top;
+ mNode.mScrollX = scrollX;
+ mNode.mScrollY = scrollY;
+ mNode.mWidth = width;
+ mNode.mHeight = height;
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_VISIBILITY_MASK) | visibility;
+ }
+
+ @Override
+ public void setEnabled(boolean state) {
+ mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_DISABLED)
+ | (state ? 0 : ViewNode.FLAGS_DISABLED);
+ }
+
+ @Override
+ public void setClickable(boolean state) {
+ mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CLICKABLE)
+ | (state ? ViewNode.FLAGS_CLICKABLE : 0);
+ }
+
+ @Override
+ public void setLongClickable(boolean state) {
+ mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_LONG_CLICKABLE)
+ | (state ? ViewNode.FLAGS_LONG_CLICKABLE : 0);
+ }
+
+ @Override
+ public void setFocusable(boolean state) {
+ mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_FOCUSABLE)
+ | (state ? ViewNode.FLAGS_FOCUSABLE : 0);
+ }
+
+ @Override
+ public void setFocused(boolean state) {
+ mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_FOCUSED)
+ | (state ? ViewNode.FLAGS_FOCUSED : 0);
+ }
+
+ @Override
+ public void setAccessibilityFocused(boolean state) {
+ mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_ACCESSIBILITY_FOCUSED)
+ | (state ? ViewNode.FLAGS_ACCESSIBILITY_FOCUSED : 0);
+ }
+
+ @Override
+ public void setCheckable(boolean state) {
+ mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CHECKABLE)
+ | (state ? ViewNode.FLAGS_CHECKABLE : 0);
+ }
+
+ @Override
+ public void setChecked(boolean state) {
+ mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_CHECKED)
+ | (state ? ViewNode.FLAGS_CHECKED : 0);
+ }
+
+ @Override
+ public void setSelected(boolean state) {
+ mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_SELECTED)
+ | (state ? ViewNode.FLAGS_SELECTED : 0);
+ }
+
+ @Override
+ public void setActivated(boolean state) {
+ mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_ACTIVATED)
+ | (state ? ViewNode.FLAGS_ACTIVATED : 0);
+ }
+
+ @Override
+ public void setClassName(String className) {
+ mNode.mClassName = className;
+ }
+
+ @Override
+ public void setContentDescription(CharSequence contentDescription) {
+ mNode.mContentDescription = contentDescription;
+ }
+
+ private final ViewNodeText getNodeText() {
+ if (mNode.mText != null) {
+ return mNode.mText;
+ }
+ mNode.mText = new ViewNodeText();
+ return mNode.mText;
+ }
+
+ @Override
+ public void setText(CharSequence text) {
+ ViewNodeText t = getNodeText();
+ t.mText = text;
+ t.mTextSelectionStart = t.mTextSelectionEnd = -1;
+ }
+
+ @Override
+ public void setText(CharSequence text, int selectionStart, int selectionEnd) {
+ ViewNodeText t = getNodeText();
+ t.mText = text;
+ t.mTextSelectionStart = selectionStart;
+ t.mTextSelectionEnd = selectionEnd;
+ }
+
+ @Override
+ public void setTextPaint(TextPaint paint) {
+ ViewNodeText t = getNodeText();
+ t.mTextColor = paint.getColor();
+ t.mTextBackgroundColor = paint.bgColor;
+ t.mTextSize = paint.getTextSize();
+ t.mTextStyle = 0;
+ Typeface tf = paint.getTypeface();
+ if (tf != null) {
+ if (tf.isBold()) {
+ t.mTextStyle |= ViewNode.TEXT_STYLE_BOLD;
+ }
+ if (tf.isItalic()) {
+ t.mTextStyle |= ViewNode.TEXT_STYLE_ITALIC;
+ }
+ }
+ int pflags = paint.getFlags();
+ if ((pflags& Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
+ t.mTextStyle |= ViewNode.TEXT_STYLE_BOLD;
+ }
+ if ((pflags& Paint.UNDERLINE_TEXT_FLAG) != 0) {
+ t.mTextStyle |= ViewNode.TEXT_STYLE_UNDERLINE;
+ }
+ if ((pflags& Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
+ t.mTextStyle |= ViewNode.TEXT_STYLE_STRIKE_THRU;
+ }
+ }
+
+ @Override
+ public void setHint(CharSequence hint) {
+ getNodeText().mHint = hint != null ? hint.toString() : null;
+ }
+
+ @Override
+ public CharSequence getText() {
+ return mNode.mText != null ? mNode.mText.mText : null;
+ }
+
+ @Override
+ public int getTextSelectionStart() {
+ return mNode.mText != null ? mNode.mText.mTextSelectionStart : -1;
+ }
+
+ @Override
+ public int getTextSelectionEnd() {
+ return mNode.mText != null ? mNode.mText.mTextSelectionEnd : -1;
+ }
+
+ @Override
+ public CharSequence getHint() {
+ return mNode.mText != null ? mNode.mText.mHint : null;
+ }
+
+ @Override
+ public Bundle editExtras() {
+ if (mNode.mExtras != null) {
+ return mNode.mExtras;
+ }
+ mNode.mExtras = new Bundle();
+ return mNode.mExtras;
+ }
+
+ @Override
+ public void clearExtras() {
+ mNode.mExtras = null;
+ }
+
+ @Override
+ public void setChildCount(int num) {
+ mNode.mChildren = new ViewNode[num];
+ }
+
+ @Override
+ public int getChildCount() {
+ return mNode.mChildren != null ? mNode.mChildren.length : 0;
+ }
+
+ @Override
+ public ViewAssistStructure newChild(int index) {
+ ViewNode node = new ViewNode();
+ mNode.mChildren[index] = node;
+ return new ViewNodeBuilder(mAssist, node, false);
+ }
+
+ @Override
+ public ViewAssistStructure asyncNewChild(int index) {
+ synchronized (mAssist) {
+ ViewNode node = new ViewNode();
+ mNode.mChildren[index] = node;
+ ViewNodeBuilder builder = new ViewNodeBuilder(mAssist, node, true);
+ mAssist.mPendingAsyncChildren.add(builder);
+ return builder;
+ }
+ }
+
+ @Override
+ public void asyncCommit() {
+ synchronized (mAssist) {
+ if (!mAsync) {
+ throw new IllegalStateException("Child " + this
+ + " was not created with ViewAssistStructure.asyncNewChild");
+ }
+ if (!mAssist.mPendingAsyncChildren.remove(this)) {
+ throw new IllegalStateException("Child " + this + " already committed");
+ }
+ mAssist.notifyAll();
+ }
+ }
+
+ @Override
+ public Rect getTempRect() {
+ return mAssist.mTmpRect;
+ }
+ }
+
AssistStructure(Activity activity) {
+ mHaveData = true;
mActivityComponent = activity.getComponentName();
ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
activity.getActivityToken());
@@ -606,13 +703,7 @@ final public class AssistStructure implements Parcelable {
}
AssistStructure(Parcel in) {
- PooledStringReader preader = new PooledStringReader(in);
- mActivityComponent = ComponentName.readFromParcel(in);
- final int N = in.readInt();
- for (int i=0; i<N; i++) {
- mWindowNodes.add(new WindowNode(in, preader));
- }
- //dump();
+ mReceiveChannel = in.readStrongBinder();
}
/** @hide */
@@ -689,6 +780,7 @@ final public class AssistStructure implements Parcelable {
}
public ComponentName getActivityComponent() {
+ ensureData();
return mActivityComponent;
}
@@ -696,6 +788,7 @@ final public class AssistStructure implements Parcelable {
* Return the number of window contents that have been collected in this assist data.
*/
public int getWindowNodeCount() {
+ ensureData();
return mWindowNodes.size();
}
@@ -704,6 +797,7 @@ final public class AssistStructure implements Parcelable {
* @param index Which window to retrieve, may be 0 to {@link #getWindowNodeCount()}-1.
*/
public WindowNode getWindowNodeAt(int index) {
+ ensureData();
return mWindowNodes.get(index);
}
@@ -711,11 +805,47 @@ final public class AssistStructure implements Parcelable {
return 0;
}
- public void writeToParcel(Parcel out, int flags) {
+ /** @hide */
+ public void ensureData() {
+ if (mHaveData) {
+ return;
+ }
+ mHaveData = true;
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(DESCRIPTOR);
+ try {
+ mReceiveChannel.transact(TRANSACTION_XFER, data, reply, 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failure reading AssistStructure data", e);
+ return;
+ }
+ readContentFromParcel(reply);
+ data.recycle();
+ reply.recycle();
+ }
+
+ void writeContentToParcel(Parcel out, int flags) {
+ // First make sure all content has been created.
+ boolean skipStructure = false;
+ synchronized (this) {
+ long endTime = SystemClock.uptimeMillis() + 5000;
+ long now;
+ while (mPendingAsyncChildren.size() > 0 && (now=SystemClock.uptimeMillis()) < endTime) {
+ try {
+ wait(endTime-now);
+ } catch (InterruptedException e) {
+ }
+ }
+ if (mPendingAsyncChildren.size() > 0) {
+ // We waited too long, assume none of the assist structure is valid.
+ skipStructure = true;
+ }
+ }
int start = out.dataPosition();
PooledStringWriter pwriter = new PooledStringWriter(out);
ComponentName.writeToParcel(mActivityComponent, out);
- final int N = mWindowNodes.size();
+ final int N = skipStructure ? 0 : mWindowNodes.size();
out.writeInt(N);
for (int i=0; i<N; i++) {
mWindowNodes.get(i).writeToParcel(out, pwriter);
@@ -724,6 +854,30 @@ final public class AssistStructure implements Parcelable {
Log.i(TAG, "Flattened assist data: " + (out.dataPosition() - start) + " bytes");
}
+ void readContentFromParcel(Parcel in) {
+ PooledStringReader preader = new PooledStringReader(in);
+ mActivityComponent = ComponentName.readFromParcel(in);
+ final int N = in.readInt();
+ for (int i=0; i<N; i++) {
+ mWindowNodes.add(new WindowNode(in, preader));
+ }
+ //dump();
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ if (mHaveData) {
+ // This object holds its data. We want to write a send channel that the
+ // other side can use to retrieve that data.
+ if (mSendChannel == null) {
+ mSendChannel = new SendChannel();
+ }
+ out.writeStrongBinder(mSendChannel);
+ } else {
+ // This object doesn't hold its data, so just propagate along its receive channel.
+ out.writeStrongBinder(mReceiveChannel);
+ }
+ }
+
public static final Parcelable.Creator<AssistStructure> CREATOR
= new Parcelable.Creator<AssistStructure>() {
public AssistStructure createFromParcel(Parcel in) {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index d794aa3..4a1d6ff 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -482,11 +482,13 @@ public interface IActivityManager extends IInterface {
public void systemBackupRestored() throws RemoteException;
public void notifyCleartextNetwork(int uid, byte[] firstPacket) throws RemoteException;
- public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException;
+ public void setDumpHeapDebugLimit(String processName, int uid, long maxMemSize,
+ String reportPackage) throws RemoteException;
public void dumpHeapFinished(String path) throws RemoteException;
public void setVoiceKeepAwake(IVoiceInteractionSession session, boolean keepAwake)
throws RemoteException;
+ public void updateLockTaskPackages(int userId, String[] packages) throws RemoteException;
/*
* Private non-Binder interfaces
@@ -822,4 +824,5 @@ public interface IActivityManager extends IInterface {
int SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+287;
int DUMP_HEAP_FINISHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+288;
int SET_VOICE_KEEP_AWAKE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+289;
+ int UPDATE_LOCK_TASK_PACKAGES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+290;
}
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 451af99..fe8e228 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -100,6 +100,11 @@ oneway interface IBackupAgent {
void doFullBackup(in ParcelFileDescriptor data, int token, IBackupManager callbackBinder);
/**
+ * Estimate how much data a full backup will deliver
+ */
+ void doMeasureFullBackup(int token, IBackupManager callbackBinder);
+
+ /**
* Restore a single "file" to the application. The file was typically obtained from
* a full-backup dataset. The agent reads 'size' bytes of file content
* from the provided file descriptor.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b31ce04..e7f8f6d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -646,6 +646,11 @@ public class Notification implements Parcelable
public static final String CATEGORY_STATUS = "status";
/**
+ * Notification category: user-scheduled reminder.
+ */
+ public static final String CATEGORY_REMINDER = "reminder";
+
+ /**
* One of the predefined notification categories (see the <code>CATEGORY_*</code> constants)
* that best describes this Notification. May be used by the system for ranking and filtering.
*/
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 59fe490..b3aa6be 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -99,8 +99,8 @@ import android.os.Vibrator;
import android.os.storage.StorageManager;
import android.print.IPrintManager;
import android.print.PrintManager;
-import android.service.fingerprint.FingerprintManager;
-import android.service.fingerprint.IFingerprintService;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.IFingerprintService;
import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
import android.telecom.TelecomManager;
@@ -402,13 +402,7 @@ final class SystemServiceRegistry {
new CachedServiceFetcher<StorageManager>() {
@Override
public StorageManager createService(ContextImpl ctx) {
- try {
- return new StorageManager(
- ctx.getContentResolver(), ctx.mMainThread.getHandler().getLooper());
- } catch (RemoteException rex) {
- Log.e(TAG, "Failed to create StorageManager", rex);
- return null;
- }
+ return new StorageManager(ctx, ctx.mMainThread.getHandler().getLooper());
}});
registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class,
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index b0dd70f..a8494fb 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -690,7 +690,7 @@ public final class UiAutomation {
* potentially undesirable actions such as calling 911 or posting on public forums etc.
*
* @param enable whether to run in a "monkey" mode or not. Default is not.
- * @see {@link android.app.ActivityManager#isUserAMonkey()}
+ * @see ActivityManager#isUserAMonkey()
*/
public void setRunAsMonkey(boolean enable) {
synchronized (mLock) {
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index da7bb05..7acf5f0 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -103,9 +103,9 @@ public class VoiceInteractor {
request = pullRequest((IVoiceInteractorRequest)args.arg1, true);
if (DEBUG) Log.d(TAG, "onCompleteVoice: req="
+ ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
- + " result=" + args.arg1);
+ + " result=" + args.arg2);
if (request != null) {
- ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg1);
+ ((CompleteVoiceRequest)request).onCompleteResult((Bundle) args.arg2);
request.clear();
}
break;
@@ -297,6 +297,7 @@ public class VoiceInteractor {
*/
public static final class Option implements Parcelable {
final CharSequence mLabel;
+ final int mIndex;
ArrayList<CharSequence> mSynonyms;
Bundle mExtras;
@@ -308,6 +309,21 @@ public class VoiceInteractor {
*/
public Option(CharSequence label) {
mLabel = label;
+ mIndex = -1;
+ }
+
+ /**
+ * Creates an option that a user can select with their voice by matching the label
+ * or one of several synonyms.
+ * @param label The label that will both be matched against what the user speaks
+ * and displayed visually.
+ * @param index The location of this option within the overall set of options.
+ * Can be used to help identify which the option when it is returned from the
+ * voice interactor.
+ */
+ public Option(CharSequence label, int index) {
+ mLabel = label;
+ mIndex = index;
}
/**
@@ -328,6 +344,14 @@ public class VoiceInteractor {
return mLabel;
}
+ /**
+ * Return the index that was supplied in the constructor.
+ * If the option was constructed without an index, -1 is returned.
+ */
+ public int getIndex() {
+ return mIndex;
+ }
+
public int countSynonyms() {
return mSynonyms != null ? mSynonyms.size() : 0;
}
@@ -356,6 +380,7 @@ public class VoiceInteractor {
Option(Parcel in) {
mLabel = in.readCharSequence();
+ mIndex = in.readInt();
mSynonyms = in.readCharSequenceList();
mExtras = in.readBundle();
}
@@ -368,6 +393,7 @@ public class VoiceInteractor {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeCharSequence(mLabel);
+ dest.writeInt(mIndex);
dest.writeCharSequenceList(mSynonyms);
dest.writeBundle(mExtras);
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ea48b61..68f4707 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -175,7 +175,8 @@ public class DevicePolicyManager {
*
* <p>This component is set as device owner and active admin when device owner provisioning is
* started by an NFC message containing an NFC record with MIME type
- * {@link #MIME_TYPE_PROVISIONING_NFC_V2}.
+ * {@link #MIME_TYPE_PROVISIONING_NFC_V2}. For the NFC record, the component name should be
+ * flattened to a string, via {@link ComponentName#flattenToShortString()}.
*
* @see DeviceAdminReceiver
*/
@@ -398,14 +399,16 @@ public class DevicePolicyManager {
"android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
/**
- * On devices managed by a device owner app, a String representation of a Component name extra
- * indicating the component of the application that is temporarily granted device owner
- * privileges during device initialization and profile owner privileges during secondary user
- * initialization.
+ * On devices managed by a device owner app, a {@link ComponentName} extra indicating the
+ * component of the application that is temporarily granted device owner privileges during
+ * device initialization and profile owner privileges during secondary user initialization.
*
- * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner
- * provisioning via an NFC bump.
- * @see ComponentName#unflattenFromString()
+ * <p>
+ * It can also be used in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts
+ * device owner provisioning via an NFC bump. For the NFC record, it should be flattened to a
+ * string first.
+ *
+ * @see ComponentName#flattenToShortString()
*/
public static final String EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME
= "android.app.extra.PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME";
@@ -499,6 +502,20 @@ public class DevicePolicyManager {
*/
public static final String EXTRA_PROVISIONING_BT_USE_PROXY
= "android.app.extra.PROVISIONING_BT_USE_PROXY";
+
+ /**
+ * A {@link android.os.Parcelable} extra of type {@link android.os.PersistableBundle} that
+ * holds data needed by the system to wipe factory reset protection. The data needed to wipe
+ * the device depend on the installed factory reset protection implementation. For example,
+ * if an account is needed to unlock a device, this extra may contain data used to
+ * authenticate that account.
+ *
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner
+ * provisioning via an NFC bump.
+ */
+ public static final String EXTRA_PROVISIONING_RESET_PROTECTION_PARAMETERS
+ = "android.app.extra.PROVISIONING_RESET_PROTECTION_PARAMETERS";
+
/**
* This MIME type is used for starting the Device Owner provisioning that does not require
* provisioning features introduced in Android API level
@@ -2636,8 +2653,8 @@ public class DevicePolicyManager {
* called by the device owner.
* @param initializer Which {@link DeviceAdminReceiver} to make device initializer.
* @param initializerName The user-visible name of the device initializer.
- * @return whether the package was successfully registered as the device initializer.
- * @throws IllegalArgumentException if the package name is null or invalid
+ * @return whether the component was successfully registered as the device initializer.
+ * @throws IllegalArgumentException if the componentname is null or invalid
* @throws IllegalStateException if the caller is not device owner or the device has
* already been provisioned or a device initializer already exists.
*/
@@ -2707,6 +2724,25 @@ public class DevicePolicyManager {
}
/**
+ * @hide
+ * Gets the device initializer component of the system.
+ *
+ * @return the component name of the device initializer.
+ */
+ @SystemApi
+ public ComponentName getDeviceInitializerComponent() {
+ if (mService != null) {
+ try {
+ return mService.getDeviceInitializerComponent();
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to get device initializer");
+ }
+ }
+ return null;
+ }
+
+
+ /**
* Sets the enabled state of the user. A user should be enabled only once it is ready to
* be used.
*
@@ -3162,6 +3198,22 @@ public class DevicePolicyManager {
}
/**
+ * Start Quick Contact on the managed profile for the current user, if the policy allows.
+ * @hide
+ */
+ public void startManagedQuickContact(String actualLookupKey, long actualContactId,
+ Intent originalIntent) {
+ if (mService != null) {
+ try {
+ mService.startManagedQuickContact(
+ actualLookupKey, actualContactId, originalIntent);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
* Called by the profile owner of a managed profile so that some intents sent in the managed
* profile can also be resolved in the parent, or vice versa.
* Only activity intents are supported.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9ca52e5..c68311e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -189,6 +189,7 @@ interface IDevicePolicyManager {
void setCrossProfileCallerIdDisabled(in ComponentName who, boolean disabled);
boolean getCrossProfileCallerIdDisabled(in ComponentName who);
boolean getCrossProfileCallerIdDisabledForUser(int userId);
+ void startManagedQuickContact(String lookupKey, long contactId, in Intent originalIntent);
void setTrustAgentConfiguration(in ComponentName admin, in ComponentName agent,
in PersistableBundle args);
@@ -209,6 +210,7 @@ interface IDevicePolicyManager {
void clearDeviceInitializer(in ComponentName who);
boolean setDeviceInitializer(in ComponentName who, in ComponentName initializer, String initializerName);
String getDeviceInitializer();
+ ComponentName getDeviceInitializerComponent();
void setUserIcon(in ComponentName admin, in Bitmap icon);
}
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 7f89100..2bf267a 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -424,10 +424,12 @@ public abstract class BackupAgent extends ContextWrapper {
}
// And now that we know where it lives, semantically, back it up appropriately
- Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
+ // In the measurement case, backupToTar() updates the size in output and returns
+ // without transmitting any file data.
+ if (DEBUG) Log.i(TAG, "backupFile() of " + filePath + " => domain=" + domain
+ " rootpath=" + rootpath);
- FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath,
- output.getData());
+
+ FullBackup.backupToTar(getPackageName(), domain, null, rootpath, filePath, output);
}
/**
@@ -477,9 +479,8 @@ public abstract class BackupAgent extends ContextWrapper {
continue;
}
- // Finally, back this file up before proceeding
- FullBackup.backupToTar(packageName, domain, null, rootPath, filePath,
- output.getData());
+ // Finally, back this file up (or measure it) before proceeding
+ FullBackup.backupToTar(packageName, domain, null, rootPath, filePath, output);
}
}
}
@@ -640,7 +641,7 @@ public abstract class BackupAgent extends ContextWrapper {
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token);
+ callbackBinder.opComplete(token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
@@ -670,7 +671,7 @@ public abstract class BackupAgent extends ContextWrapper {
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token);
+ callbackBinder.opComplete(token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
@@ -692,10 +693,10 @@ public abstract class BackupAgent extends ContextWrapper {
try {
BackupAgent.this.onFullBackup(new FullBackupDataOutput(data));
} catch (IOException ex) {
- Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw new RuntimeException(ex);
} catch (RuntimeException ex) {
- Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw ex;
} finally {
// ... and then again after, as in the doBackup() case
@@ -713,13 +714,37 @@ public abstract class BackupAgent extends ContextWrapper {
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token);
+ callbackBinder.opComplete(token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
}
}
+ public void doMeasureFullBackup(int token, IBackupManager callbackBinder) {
+ // Ensure that we're running with the app's normal permission level
+ final long ident = Binder.clearCallingIdentity();
+ FullBackupDataOutput measureOutput = new FullBackupDataOutput();
+
+ waitForSharedPrefs();
+ try {
+ BackupAgent.this.onFullBackup(measureOutput);
+ } catch (IOException ex) {
+ Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw new RuntimeException(ex);
+ } catch (RuntimeException ex) {
+ Log.d(TAG, "onFullBackup[M] (" + BackupAgent.this.getClass().getName() + ") threw", ex);
+ throw ex;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ try {
+ callbackBinder.opComplete(token, measureOutput.getSize());
+ } catch (RemoteException e) {
+ // timeout, so we're safe
+ }
+ }
+ }
+
@Override
public void doRestoreFile(ParcelFileDescriptor data, long size,
int type, String domain, String path, long mode, long mtime,
@@ -728,6 +753,7 @@ public abstract class BackupAgent extends ContextWrapper {
try {
BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime);
} catch (IOException e) {
+ Log.d(TAG, "onRestoreFile (" + BackupAgent.this.getClass().getName() + ") threw", e);
throw new RuntimeException(e);
} finally {
// Ensure that any side-effect SharedPreferences writes have landed
@@ -735,7 +761,7 @@ public abstract class BackupAgent extends ContextWrapper {
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token);
+ callbackBinder.opComplete(token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
@@ -747,13 +773,16 @@ public abstract class BackupAgent extends ContextWrapper {
long ident = Binder.clearCallingIdentity();
try {
BackupAgent.this.onRestoreFinished();
+ } catch (Exception e) {
+ Log.d(TAG, "onRestoreFinished (" + BackupAgent.this.getClass().getName() + ") threw", e);
+ throw e;
} finally {
// Ensure that any side-effect SharedPreferences writes have landed
waitForSharedPrefs();
Binder.restoreCallingIdentity(ident);
try {
- callbackBinder.opComplete(token);
+ callbackBinder.opComplete(token, 0);
} catch (RemoteException e) {
// we'll time out anyway, so we're safe
}
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index e853540..1131ff9 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -393,6 +393,26 @@ public class BackupTransport {
}
/**
+ * Called after {@link #performFullBackup} to make sure that the transport is willing to
+ * handle a full-data backup operation of the specified size on the current package.
+ * If the transport returns anything other than TRANSPORT_OK, the package's backup
+ * operation will be skipped (and {@link #finishBackup() invoked} with no data for that
+ * package being passed to {@link #sendBackupData}.
+ *
+ * Added in MNC (API 23).
+ *
+ * @param size The estimated size of the full-data payload for this app. This includes
+ * manifest and archive format overhead, but is not guaranteed to be precise.
+ * @return TRANSPORT_OK if the platform is to proceed with the full-data backup,
+ * TRANSPORT_PACKAGE_REJECTED if the proposed payload size is too large for
+ * the transport to handle, or TRANSPORT_ERROR to indicate a fatal error
+ * condition that means the platform cannot perform a backup at this time.
+ */
+ public int checkFullBackupSize(long size) {
+ return BackupTransport.TRANSPORT_OK;
+ }
+
+ /**
* Tells the transport to read {@code numBytes} bytes of data from the socket file
* descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}
* call, and deliver those bytes to the datastore.
@@ -444,7 +464,7 @@ public class BackupTransport {
* transport level).
*
* <p>After this method returns zero, the system will then call
- * {@link #getNextFullRestorePackage()} to begin the restore process for the next
+ * {@link #nextRestorePackage()} to begin the restore process for the next
* application, and the sequence begins again.
*
* <p>The transport should always close this socket when returning from this method.
@@ -588,6 +608,11 @@ public class BackupTransport {
}
@Override
+ public int checkFullBackupSize(long size) {
+ return BackupTransport.this.checkFullBackupSize(size);
+ }
+
+ @Override
public int sendBackupData(int numBytes) throws RemoteException {
return BackupTransport.this.sendBackupData(numBytes);
}
diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java
index e5b47c6..259884e 100644
--- a/core/java/android/app/backup/FullBackup.java
+++ b/core/java/android/app/backup/FullBackup.java
@@ -58,7 +58,7 @@ public class FullBackup {
* @hide
*/
static public native int backupToTar(String packageName, String domain,
- String linkdomain, String rootpath, String path, BackupDataOutput output);
+ String linkdomain, String rootpath, String path, FullBackupDataOutput output);
/**
* Copy data from a socket to the given File location on permanent storage. The
diff --git a/core/java/android/app/backup/FullBackupDataOutput.java b/core/java/android/app/backup/FullBackupDataOutput.java
index 99dab1f..94704b9 100644
--- a/core/java/android/app/backup/FullBackupDataOutput.java
+++ b/core/java/android/app/backup/FullBackupDataOutput.java
@@ -9,7 +9,14 @@ import android.os.ParcelFileDescriptor;
*/
public class FullBackupDataOutput {
// Currently a name-scoping shim around BackupDataOutput
- private BackupDataOutput mData;
+ private final BackupDataOutput mData;
+ private long mSize;
+
+ /** @hide - used only in measure operation */
+ public FullBackupDataOutput() {
+ mData = null;
+ mSize = 0;
+ }
/** @hide */
public FullBackupDataOutput(ParcelFileDescriptor fd) {
@@ -18,4 +25,14 @@ public class FullBackupDataOutput {
/** @hide */
public BackupDataOutput getData() { return mData; }
+
+ /** @hide - used for measurement pass */
+ public void addSize(long size) {
+ if (size > 0) {
+ mSize += size;
+ }
+ }
+
+ /** @hide - used for measurement pass */
+ public long getSize() { return mSize; }
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 41ad936..8f36dc4 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -286,11 +286,14 @@ interface IBackupManager {
* Notify the backup manager that a BackupAgent has completed the operation
* corresponding to the given token.
*
- * @param token The transaction token passed to a BackupAgent's doBackup() or
- * doRestore() method.
+ * @param token The transaction token passed to the BackupAgent method being
+ * invoked.
+ * @param result In the case of a full backup measure operation, the estimated
+ * total file size that would result from the operation. Unused in all other
+ * cases.
* {@hide}
*/
- void opComplete(int token);
+ void opComplete(int token, long result);
/**
* Make the device's backup and restore machinery (in)active. When it is inactive,
diff --git a/core/java/android/app/backup/RecentsBackupHelper.java b/core/java/android/app/backup/RecentsBackupHelper.java
index fd69d20..1a64da6 100644
--- a/core/java/android/app/backup/RecentsBackupHelper.java
+++ b/core/java/android/app/backup/RecentsBackupHelper.java
@@ -1,3 +1,19 @@
+/*
+ * 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.app.backup;
import android.content.Context;
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 0122069..8b3fc2e 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -57,4 +57,41 @@ public abstract class UsageStatsManagerInternal {
* Prepares the UsageStatsService for shutdown.
*/
public abstract void prepareShutdown();
+
+ /**
+ * Returns true if the app has not been used for a certain amount of time. How much time?
+ * Could be hours, could be days, who knows?
+ *
+ * @param packageName
+ * @param userId
+ * @return
+ */
+ public abstract boolean isAppIdle(String packageName, int userId);
+
+ /**
+ * Returns the most recent time that the specified package was active for the given user.
+ * @param packageName The package to search.
+ * @param userId The user id of the user of interest.
+ * @return The timestamp of when the package was last used, or -1 if it hasn't been used.
+ */
+ public abstract long getLastPackageAccessTime(String packageName, int userId);
+
+ /**
+ * Sets up a listener for changes to packages being accessed.
+ * @param listener A listener within the system process.
+ */
+ public abstract void addAppIdleStateChangeListener(
+ AppIdleStateChangeListener listener);
+
+ /**
+ * Removes a listener that was previously added for package usage state changes.
+ * @param listener The listener within the system process to remove.
+ */
+ public abstract void removeAppIdleStateChangeListener(
+ AppIdleStateChangeListener listener);
+
+ public interface AppIdleStateChangeListener {
+ void onAppIdleStateChanged(String packageName, int userId, boolean idle);
+ }
+
}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index be26eac..edb768d 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -18,6 +18,7 @@ package android.bluetooth;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
@@ -206,6 +207,23 @@ public final class BluetoothAdapter {
"android.bluetooth.adapter.action.REQUEST_ENABLE";
/**
+ * Activity Action: Show a system activity that allows user to enable BLE scans even when
+ * Bluetooth is turned off.<p>
+ *
+ * Notification of result of this activity is posted using
+ * {@link android.app.Activity#onActivityResult}. The <code>resultCode</code> will be
+ * {@link android.app.Activity#RESULT_OK} if BLE scan always available setting is turned on or
+ * {@link android.app.Activity#RESULT_CANCELED} if the user has rejected the request or an
+ * error occurred.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE =
+ "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
+
+ /**
* Broadcast Action: Indicates the Bluetooth scan mode of the local Adapter
* has changed.
* <p>Always contains the extra fields {@link #EXTRA_SCAN_MODE} and {@link
@@ -916,6 +934,22 @@ public final class BluetoothAdapter {
}
/**
+ * Returns {@code true} if BLE scan is always available, {@code false} otherwise. <p>
+ *
+ * If this returns {@code true}, application can issue {@link BluetoothLeScanner#startScan} and
+ * fetch scan results even when Bluetooth is turned off.<p>
+ *
+ * To change this setting, use {@link #ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean isBleScanAlwaysAvailable() {
+ // TODO: implement after Settings UI change.
+ return false;
+ }
+
+ /**
* Returns whether peripheral mode is supported.
*
* @hide
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index e9d4e59..9450dce 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -16,6 +16,7 @@
package android.content;
+import android.annotation.AttrRes;
import android.annotation.CheckResult;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -474,8 +475,7 @@ public abstract class Context {
*
* @see android.content.res.Resources.Theme#obtainStyledAttributes(int[])
*/
- public final TypedArray obtainStyledAttributes(
- int[] attrs) {
+ public final TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) {
return getTheme().obtainStyledAttributes(attrs);
}
@@ -487,7 +487,7 @@ public abstract class Context {
* @see android.content.res.Resources.Theme#obtainStyledAttributes(int, int[])
*/
public final TypedArray obtainStyledAttributes(
- @StyleableRes int resid, int[] attrs) throws Resources.NotFoundException {
+ @StyleRes int resid, @StyleableRes int[] attrs) throws Resources.NotFoundException {
return getTheme().obtainStyledAttributes(resid, attrs);
}
@@ -499,7 +499,7 @@ public abstract class Context {
* @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
*/
public final TypedArray obtainStyledAttributes(
- AttributeSet set, int[] attrs) {
+ AttributeSet set, @StyleableRes int[] attrs) {
return getTheme().obtainStyledAttributes(set, attrs, 0, 0);
}
@@ -511,7 +511,8 @@ public abstract class Context {
* @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
*/
public final TypedArray obtainStyledAttributes(
- AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
+ AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
+ @StyleRes int defStyleRes) {
return getTheme().obtainStyledAttributes(
set, attrs, defStyleAttr, defStyleRes);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 030b770..eea47b7 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1041,6 +1041,18 @@ public class Intent implements Parcelable, Cloneable {
*/
public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";
/**
+ * Activity action: Activate the current SIM card. If SIM cards do not require activation,
+ * sending this intent is a no-op.
+ * <p>Input: No data should be specified. get*Extra may have an optional
+ * {@link #EXTRA_SIM_ACTIVATION_RESPONSE} field containing a PendingIntent through which to
+ * send the activation result.
+ * <p>Output: nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SIM_ACTIVATION_REQUEST =
+ "android.intent.action.SIM_ACTIVATION_REQUEST";
+ /**
* Activity Action: Send a message to someone specified by the data.
* <p>Input: {@link #getData} is URI describing the target.
* <p>Output: nothing.
@@ -1911,6 +1923,19 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_PACKAGE_VERIFIED = "android.intent.action.PACKAGE_VERIFIED";
/**
+ * Broadcast Action: Sent to the system intent filter verifier when an intent filter
+ * needs to be verified. The data contains the filter data hosts to be verified against.
+ * <p class="note">
+ * This is a protected intent that can only be sent by the system.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+
+ /**
* Broadcast Action: Resources for a set of packages (which were
* previously unavailable) are currently
* available since the media on which they exist is available.
@@ -2883,6 +2908,24 @@ public class Intent implements Parcelable, Cloneable {
/** {@hide} */
public static final String EXTRA_SETTING_NEW_VALUE = "new_value";
+ /**
+ * Activity Action: Process a piece of text.
+ * <p>Input: {@link #EXTRA_PROCESS_TEXT} contains the text to be processed.
+ * {@link #EXTRA_PROCESS_TEXT_READONLY} states if the resulting text will be read-only.</p>
+ * <p>Output: {@link #EXTRA_PROCESS_TEXT} contains the processed text.</p>
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_PROCESS_TEXT = "android.intent.action.PROCESS_TEXT";
+ /**
+ * The name of the extra used to define the text to be processed.
+ */
+ public static final String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
+ /**
+ * The name of the extra used to define if the processed text will be used as read-only.
+ */
+ public static final String EXTRA_PROCESS_TEXT_READONLY =
+ "android.intent.extra.PROCESS_TEXT_READONLY";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -3607,6 +3650,15 @@ public class Intent implements Parcelable, Cloneable {
/** {@hide} */
public static final String EXTRA_REASON = "android.intent.extra.REASON";
+ /**
+ * Optional {@link android.app.PendingIntent} extra used to deliver the result of the SIM
+ * activation request.
+ * TODO: Add information about the structure and response data used with the pending intent.
+ * @hide
+ */
+ public static final String EXTRA_SIM_ACTIVATION_RESPONSE =
+ "android.intent.extra.SIM_ACTIVATION_RESPONSE";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index 1240a23..590d791 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -16,10 +16,12 @@
package android.content;
+import android.content.pm.PackageParser;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PatternMatcher;
+import android.text.TextUtils;
import android.util.AndroidException;
import android.util.Log;
import android.util.Printer;
@@ -150,6 +152,7 @@ public class IntentFilter implements Parcelable {
private static final String CAT_STR = "cat";
private static final String NAME_STR = "name";
private static final String ACTION_STR = "action";
+ private static final String AUTO_VERIFY_STR = "autoVerify";
/**
* The filter {@link #setPriority} value at which system high-priority
@@ -247,6 +250,19 @@ public class IntentFilter implements Parcelable {
*/
public static final int NO_MATCH_CATEGORY = -4;
+ /**
+ * HTTP scheme.
+ *
+ * @see #addDataScheme(String)
+ */
+ public static final String SCHEME_HTTP = "http";
+ /**
+ * HTTPS scheme.
+ *
+ * @see #addDataScheme(String)
+ */
+ public static final String SCHEME_HTTPS = "https";
+
private int mPriority;
private final ArrayList<String> mActions;
private ArrayList<String> mCategories = null;
@@ -257,6 +273,13 @@ public class IntentFilter implements Parcelable {
private ArrayList<String> mDataTypes = null;
private boolean mHasPartialTypes = false;
+ private static final int STATE_VERIFY_AUTO = 0x00000001;
+ private static final int STATE_NEED_VERIFY = 0x00000010;
+ private static final int STATE_NEED_VERIFY_CHECKED = 0x00000100;
+ private static final int STATE_VERIFIED = 0x00001000;
+
+ private int mVerifyState;
+
// These functions are the start of more optimized code for managing
// the string sets... not yet implemented.
@@ -326,7 +349,7 @@ public class IntentFilter implements Parcelable {
public MalformedMimeTypeException(String name) {
super(name);
}
- };
+ }
/**
* Create a new IntentFilter instance with a specified action and MIME
@@ -421,6 +444,7 @@ public class IntentFilter implements Parcelable {
mDataPaths = new ArrayList<PatternMatcher>(o.mDataPaths);
}
mHasPartialTypes = o.mHasPartialTypes;
+ mVerifyState = o.mVerifyState;
}
/**
@@ -452,6 +476,94 @@ public class IntentFilter implements Parcelable {
}
/**
+ * Set whether this filter will needs to be automatically verified against its data URIs or not.
+ * The default is false.
+ *
+ * The verification would need to happen only and only if the Intent action is
+ * {@link android.content.Intent#ACTION_VIEW} and the Intent category is
+ * {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent data scheme
+ * is "http" or "https".
+ *
+ * True means that the filter will need to use its data URIs to be verified.
+ *
+ * @param autoVerify The new autoVerify value.
+ *
+ * @see #getAutoVerify()
+ * @see #addAction(String)
+ * @see #getAction(int)
+ * @see #addCategory(String)
+ * @see #getCategory(int)
+ * @see #addDataScheme(String)
+ * @see #getDataScheme(int)
+ *
+ * @hide
+ */
+ public final void setAutoVerify(boolean autoVerify) {
+ mVerifyState &= ~STATE_VERIFY_AUTO;
+ if (autoVerify) mVerifyState |= STATE_VERIFY_AUTO;
+ }
+
+ /**
+ * Return if this filter will needs to be automatically verified again its data URIs or not.
+ *
+ * @return True if the filter will needs to be automatically verified. False otherwise.
+ *
+ * @see #setAutoVerify(boolean)
+ *
+ * @hide
+ */
+ public final boolean getAutoVerify() {
+ return ((mVerifyState & STATE_VERIFY_AUTO) == 1);
+ }
+
+ /**
+ * Return if this filter needs to be automatically verified again its data URIs or not.
+ *
+ * @return True if the filter needs to be automatically verified. False otherwise.
+ *
+ * This will check if if the Intent action is {@link android.content.Intent#ACTION_VIEW} and
+ * the Intent category is {@link android.content.Intent#CATEGORY_BROWSABLE} and the Intent
+ * data scheme is "http" or "https".
+ *
+ * @see #setAutoVerify(boolean)
+ *
+ * @hide
+ */
+ public final boolean needsVerification() {
+ return hasAction(Intent.ACTION_VIEW) &&
+ hasCategory(Intent.CATEGORY_BROWSABLE) &&
+ (hasDataScheme(SCHEME_HTTP) || hasDataScheme(SCHEME_HTTPS)) &&
+ getAutoVerify();
+ }
+
+ /**
+ * Return if this filter has been verified
+ *
+ * @return true if the filter has been verified or if autoVerify is false.
+ *
+ * @hide
+ */
+ public final boolean isVerified() {
+ if ((mVerifyState & STATE_NEED_VERIFY_CHECKED) == STATE_NEED_VERIFY_CHECKED) {
+ return ((mVerifyState & STATE_NEED_VERIFY) == STATE_NEED_VERIFY);
+ }
+ return false;
+ }
+
+ /**
+ * Set if this filter has been verified
+ *
+ * @param verified true if this filter has been verified. False otherwise.
+ *
+ * @hide
+ */
+ public void setVerified(boolean verified) {
+ mVerifyState |= STATE_NEED_VERIFY_CHECKED;
+ mVerifyState &= ~STATE_VERIFIED;
+ if (verified) mVerifyState |= STATE_VERIFIED;
+ }
+
+ /**
* Add a new Intent action to match against. If any actions are included
* in the filter, then an Intent's action must be one of those values for
* it to match. If no actions are included, the Intent action is ignored.
@@ -1333,6 +1445,7 @@ public class IntentFilter implements Parcelable {
* Write the contents of the IntentFilter as an XML stream.
*/
public void writeToXml(XmlSerializer serializer) throws IOException {
+ serializer.attribute(null, AUTO_VERIFY_STR, Boolean.toString(getAutoVerify()));
int N = countActions();
for (int i=0; i<N; i++) {
serializer.startTag(null, ACTION_STR);
@@ -1407,6 +1520,9 @@ public class IntentFilter implements Parcelable {
public void readFromXml(XmlPullParser parser) throws XmlPullParserException,
IOException {
+ String autoVerify = parser.getAttributeValue(null, AUTO_VERIFY_STR);
+ setAutoVerify(TextUtils.isEmpty(autoVerify) ? false : Boolean.getBoolean(autoVerify));
+
int outerDepth = parser.getDepth();
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -1548,6 +1664,11 @@ public class IntentFilter implements Parcelable {
sb.append(", mHasPartialTypes="); sb.append(mHasPartialTypes);
du.println(sb.toString());
}
+ {
+ sb.setLength(0);
+ sb.append(prefix); sb.append("AutoVerify="); sb.append(getAutoVerify());
+ du.println(sb.toString());
+ }
}
public static final Parcelable.Creator<IntentFilter> CREATOR
@@ -1614,6 +1735,7 @@ public class IntentFilter implements Parcelable {
}
dest.writeInt(mPriority);
dest.writeInt(mHasPartialTypes ? 1 : 0);
+ dest.writeInt(getAutoVerify() ? 1 : 0);
}
/**
@@ -1680,6 +1802,7 @@ public class IntentFilter implements Parcelable {
}
mPriority = source.readInt();
mHasPartialTypes = source.readInt() > 0;
+ setAutoVerify(source.readInt() > 0);
}
private final boolean findMimeType(String type) {
@@ -1724,4 +1847,27 @@ public class IntentFilter implements Parcelable {
return false;
}
+
+ /**
+ * @hide
+ */
+ public ArrayList<String> getHostsList() {
+ ArrayList<String> result = new ArrayList<>();
+ Iterator<IntentFilter.AuthorityEntry> it = authoritiesIterator();
+ if (it != null) {
+ while (it.hasNext()) {
+ IntentFilter.AuthorityEntry entry = it.next();
+ result.add(entry.getHost());
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @hide
+ */
+ public String[] getHosts() {
+ ArrayList<String> list = getHostsList();
+ return list.toArray(new String[list.size()]);
+ }
}
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
index 6d79626..342ee38 100644
--- a/core/java/android/content/RestrictionEntry.java
+++ b/core/java/android/content/RestrictionEntry.java
@@ -20,6 +20,9 @@ import android.annotation.ArrayRes;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Arrays;
+import java.util.Objects;
+
/**
* Applications can expose restrictions for a restricted user on a
* multiuser device. The administrator can configure these restrictions that will then be
@@ -33,19 +36,19 @@ import android.os.Parcelable;
public class RestrictionEntry implements Parcelable {
/**
- * A type of restriction. Use this type for information that needs to be transferred across
- * but shouldn't be presented to the user in the UI. Stores a single String value.
+ * Hidden restriction type. Use this type for information that needs to be transferred
+ * across but shouldn't be presented to the user in the UI. Stores a single String value.
*/
public static final int TYPE_NULL = 0;
/**
- * A type of restriction. Use this for storing a boolean value, typically presented as
+ * Restriction of type "bool". Use this for storing a boolean value, typically presented as
* a checkbox in the UI.
*/
public static final int TYPE_BOOLEAN = 1;
/**
- * A type of restriction. Use this for storing a string value, typically presented as
+ * Restriction of type "choice". Use this for storing a string value, typically presented as
* a single-select list. Call {@link #setChoiceEntries(String[])} and
* {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
* and the corresponding values, respectively.
@@ -53,7 +56,7 @@ public class RestrictionEntry implements Parcelable {
public static final int TYPE_CHOICE = 2;
/**
- * A type of restriction. Use this for storing a string value, typically presented as
+ * Internal restriction type. Use this for storing a string value, typically presented as
* a single-select list. Call {@link #setChoiceEntries(String[])} and
* {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
* and the corresponding values, respectively.
@@ -64,8 +67,8 @@ public class RestrictionEntry implements Parcelable {
public static final int TYPE_CHOICE_LEVEL = 3;
/**
- * A type of restriction. Use this for presenting a multi-select list where more than one
- * entry can be selected, such as for choosing specific titles to white-list.
+ * Restriction of type "multi-select". Use this for presenting a multi-select list where more
+ * than one entry can be selected, such as for choosing specific titles to white-list.
* Call {@link #setChoiceEntries(String[])} and
* {@link #setChoiceValues(String[])} to set the localized list entries to present to the user
* and the corresponding values, respectively.
@@ -75,18 +78,30 @@ public class RestrictionEntry implements Parcelable {
public static final int TYPE_MULTI_SELECT = 4;
/**
- * A type of restriction. Use this for storing an integer value. The range of values
+ * Restriction of type "integer". Use this for storing an integer value. The range of values
* is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}.
*/
public static final int TYPE_INTEGER = 5;
/**
- * A type of restriction. Use this for storing a string value.
+ * Restriction of type "string". Use this for storing a string value.
* @see #setSelectedString
* @see #getSelectedString
*/
public static final int TYPE_STRING = 6;
+ /**
+ * Restriction of type "bundle". Use this for storing {@link android.os.Bundle bundles} of
+ * restrictions
+ */
+ public static final int TYPE_BUNDLE = 7;
+
+ /**
+ * Restriction of type "bundle_array". Use this for storing arrays of
+ * {@link android.os.Bundle bundles} of restrictions
+ */
+ public static final int TYPE_BUNDLE_ARRAY = 8;
+
/** The type of restriction. */
private int mType;
@@ -100,13 +115,13 @@ public class RestrictionEntry implements Parcelable {
private String mDescription;
/** The user-visible set of choices used for single-select and multi-select lists. */
- private String [] mChoiceEntries;
+ private String[] mChoiceEntries;
/** The values corresponding to the user-visible choices. The value(s) of this entry will
* one or more of these, returned by {@link #getAllSelectedStrings()} and
* {@link #getSelectedString()}.
*/
- private String [] mChoiceValues;
+ private String[] mChoiceValues;
/* The chosen value, whose content depends on the type of the restriction. */
private String mCurrentValue;
@@ -115,6 +130,12 @@ public class RestrictionEntry implements Parcelable {
private String[] mCurrentValues;
/**
+ * List of nested restrictions. Used by {@link #TYPE_BUNDLE bundle} and
+ * {@link #TYPE_BUNDLE_ARRAY bundle_array} restrictions.
+ */
+ private RestrictionEntry[] mRestrictions;
+
+ /**
* Constructor for specifying the type and key, with no initial value;
*
* @param type the restriction type.
@@ -170,6 +191,35 @@ public class RestrictionEntry implements Parcelable {
}
/**
+ * Constructor for {@link #TYPE_BUNDLE}/{@link #TYPE_BUNDLE_ARRAY} type.
+ * @param key the unique key for this restriction
+ * @param restrictionEntries array of nested restriction entries. If the entry, being created
+ * represents a {@link #TYPE_BUNDLE_ARRAY bundle-array}, {@code restrictionEntries} array may
+ * only contain elements of type {@link #TYPE_BUNDLE bundle}.
+ * @param isBundleArray true if this restriction represents
+ * {@link #TYPE_BUNDLE_ARRAY bundle-array} type, otherwise the type will be set to
+ * {@link #TYPE_BUNDLE bundle}.
+ */
+ public RestrictionEntry(String key, RestrictionEntry[] restrictionEntries,
+ boolean isBundleArray) {
+ mKey = key;
+ if (isBundleArray) {
+ mType = TYPE_BUNDLE_ARRAY;
+ if (restrictionEntries != null) {
+ for (RestrictionEntry restriction : restrictionEntries) {
+ if (restriction.getType() != TYPE_BUNDLE) {
+ throw new IllegalArgumentException("bundle_array restriction can only have "
+ + "nested restriction entries of type bundle");
+ }
+ }
+ }
+ } else {
+ mType = TYPE_BUNDLE;
+ }
+ setRestrictions(restrictionEntries);
+ }
+
+ /**
* Sets the type for this restriction.
* @param type the type for this restriction.
*/
@@ -283,6 +333,22 @@ public class RestrictionEntry implements Parcelable {
}
/**
+ * Returns array of possible restriction entries that this entry may contain.
+ */
+ public RestrictionEntry[] getRestrictions() {
+ return mRestrictions;
+ }
+
+ /**
+ * Sets an array of possible restriction entries, that this entry may contain.
+ * <p>This method is only relevant for types {@link #TYPE_BUNDLE} and
+ * {@link #TYPE_BUNDLE_ARRAY}
+ */
+ public void setRestrictions(RestrictionEntry[] restrictions) {
+ mRestrictions = restrictions;
+ }
+
+ /**
* Returns the list of possible string values set earlier.
* @return the list of possible values.
*/
@@ -362,27 +428,30 @@ public class RestrictionEntry implements Parcelable {
this.mTitle = title;
}
- private boolean equalArrays(String[] one, String[] other) {
- if (one.length != other.length) return false;
- for (int i = 0; i < one.length; i++) {
- if (!one[i].equals(other[i])) return false;
- }
- return true;
- }
-
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof RestrictionEntry)) return false;
final RestrictionEntry other = (RestrictionEntry) o;
- // Make sure that either currentValue matches or currentValues matches.
- return mType == other.mType && mKey.equals(other.mKey)
- &&
- ((mCurrentValues == null && other.mCurrentValues == null
- && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue))
- ||
- (mCurrentValue == null && other.mCurrentValue == null
- && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues)));
+ if (mType != other.mType || mKey.equals(other.mKey)) {
+ return false;
+ }
+ if (mCurrentValues == null && other.mCurrentValues == null
+ && mRestrictions == null && other.mRestrictions == null
+ && Objects.equals(mCurrentValue, other.mCurrentValue)) {
+ return true;
+ }
+ if (mCurrentValue == null && other.mCurrentValue == null
+ && mRestrictions == null && other.mRestrictions == null
+ && Arrays.equals(mCurrentValues, other.mCurrentValues)) {
+ return true;
+ }
+ if (mCurrentValue == null && other.mCurrentValue == null
+ && mCurrentValue == null && other.mCurrentValue == null
+ && Arrays.equals(mRestrictions, other.mRestrictions)) {
+ return true;
+ }
+ return false;
}
@Override
@@ -397,28 +466,28 @@ public class RestrictionEntry implements Parcelable {
result = 31 * result + value.hashCode();
}
}
+ } else if (mRestrictions != null) {
+ result = 31 * result + Arrays.hashCode(mRestrictions);
}
return result;
}
- private String[] readArray(Parcel in) {
- int count = in.readInt();
- String[] values = new String[count];
- for (int i = 0; i < count; i++) {
- values[i] = in.readString();
- }
- return values;
- }
-
public RestrictionEntry(Parcel in) {
mType = in.readInt();
mKey = in.readString();
mTitle = in.readString();
mDescription = in.readString();
- mChoiceEntries = readArray(in);
- mChoiceValues = readArray(in);
+ mChoiceEntries = in.readStringArray();
+ mChoiceValues = in.readStringArray();
mCurrentValue = in.readString();
- mCurrentValues = readArray(in);
+ mCurrentValues = in.readStringArray();
+ Parcelable[] parcelables = in.readParcelableArray(null);
+ if (parcelables != null) {
+ mRestrictions = new RestrictionEntry[parcelables.length];
+ for (int i = 0; i < parcelables.length; i++) {
+ mRestrictions[i] = (RestrictionEntry) parcelables[i];
+ }
+ }
}
@Override
@@ -426,27 +495,17 @@ public class RestrictionEntry implements Parcelable {
return 0;
}
- private void writeArray(Parcel dest, String[] values) {
- if (values == null) {
- dest.writeInt(0);
- } else {
- dest.writeInt(values.length);
- for (int i = 0; i < values.length; i++) {
- dest.writeString(values[i]);
- }
- }
- }
-
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeString(mKey);
dest.writeString(mTitle);
dest.writeString(mDescription);
- writeArray(dest, mChoiceEntries);
- writeArray(dest, mChoiceValues);
+ dest.writeStringArray(mChoiceEntries);
+ dest.writeStringArray(mChoiceValues);
dest.writeString(mCurrentValue);
- writeArray(dest, mCurrentValues);
+ dest.writeStringArray(mCurrentValues);
+ dest.writeParcelableArray(mRestrictions, 0);
}
public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
@@ -461,6 +520,16 @@ public class RestrictionEntry implements Parcelable {
@Override
public String toString() {
- return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}";
+ return "RestrictionEntry{" +
+ "mType=" + mType +
+ ", mKey='" + mKey + '\'' +
+ ", mTitle='" + mTitle + '\'' +
+ ", mDescription='" + mDescription + '\'' +
+ ", mChoiceEntries=" + Arrays.toString(mChoiceEntries) +
+ ", mChoiceValues=" + Arrays.toString(mChoiceValues) +
+ ", mCurrentValue='" + mCurrentValue + '\'' +
+ ", mCurrentValues=" + Arrays.toString(mCurrentValues) +
+ ", mRestrictions=" + Arrays.toString(mRestrictions) +
+ '}';
}
}
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
index 21a6a0d..1fac06e 100644
--- a/core/java/android/content/RestrictionsManager.java
+++ b/core/java/android/content/RestrictionsManager.java
@@ -32,12 +32,14 @@ import android.util.Log;
import android.util.Xml;
import com.android.internal.R;
+import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
@@ -71,12 +73,15 @@ import java.util.List;
* android:key="string"
* android:title="string resource"
* android:restrictionType=["bool" | "string" | "integer"
- * | "choice" | "multi-select" | "hidden"]
+ * | "choice" | "multi-select" | "hidden"
+ * | "bundle" | "bundle_array"]
* android:description="string resource"
* android:entries="string-array resource"
* android:entryValues="string-array resource"
- * android:defaultValue="reference"
- * /&gt;
+ * android:defaultValue="reference" &gt;
+ * &lt;restriction ... /&gt;
+ * ...
+ * &lt;/restriction&gt;
* &lt;restriction ... /&gt;
* ...
* &lt;/restrictions&gt;
@@ -97,6 +102,9 @@ import java.util.List;
* administrator controlling the values, if the title is not sufficient.</li>
* </ul>
* <p>
+ * Only restrictions of type {@code bundle} and {@code bundle_array} can have one or multiple nested
+ * restriction elements.
+ * <p>
* In your manifest's <code>application</code> section, add the meta-data tag to point to
* the restrictions XML file as shown below:
* <pre>
@@ -537,9 +545,7 @@ public class RestrictionsManager {
XmlResourceParser xml =
appInfo.loadXmlMetaData(mContext.getPackageManager(), META_DATA_APP_RESTRICTIONS);
- List<RestrictionEntry> restrictions = loadManifestRestrictions(packageName, xml);
-
- return restrictions;
+ return loadManifestRestrictions(packageName, xml);
}
private List<RestrictionEntry> loadManifestRestrictions(String packageName,
@@ -550,23 +556,16 @@ public class RestrictionsManager {
} catch (NameNotFoundException nnfe) {
return null;
}
- ArrayList<RestrictionEntry> restrictions = new ArrayList<RestrictionEntry>();
+ ArrayList<RestrictionEntry> restrictions = new ArrayList<>();
RestrictionEntry restriction;
try {
int tagType = xml.next();
while (tagType != XmlPullParser.END_DOCUMENT) {
if (tagType == XmlPullParser.START_TAG) {
- if (xml.getName().equals(TAG_RESTRICTION)) {
- AttributeSet attrSet = Xml.asAttributeSet(xml);
- if (attrSet != null) {
- TypedArray a = appContext.obtainStyledAttributes(attrSet,
- com.android.internal.R.styleable.RestrictionEntry);
- restriction = loadRestriction(appContext, a);
- if (restriction != null) {
- restrictions.add(restriction);
- }
- }
+ restriction = loadRestrictionElement(appContext, xml);
+ if (restriction != null) {
+ restrictions.add(restriction);
}
}
tagType = xml.next();
@@ -582,7 +581,21 @@ public class RestrictionsManager {
return restrictions;
}
- private RestrictionEntry loadRestriction(Context appContext, TypedArray a) {
+ private RestrictionEntry loadRestrictionElement(Context appContext, XmlResourceParser xml)
+ throws IOException, XmlPullParserException {
+ if (xml.getName().equals(TAG_RESTRICTION)) {
+ AttributeSet attrSet = Xml.asAttributeSet(xml);
+ if (attrSet != null) {
+ TypedArray a = appContext.obtainStyledAttributes(attrSet,
+ com.android.internal.R.styleable.RestrictionEntry);
+ return loadRestriction(appContext, a, xml);
+ }
+ }
+ return null;
+ }
+
+ private RestrictionEntry loadRestriction(Context appContext, TypedArray a, XmlResourceParser xml)
+ throws IOException, XmlPullParserException {
String key = a.getString(R.styleable.RestrictionEntry_key);
int restrictionType = a.getInt(
R.styleable.RestrictionEntry_restrictionType, -1);
@@ -633,9 +646,90 @@ public class RestrictionsManager {
restriction.setSelectedState(
a.getBoolean(R.styleable.RestrictionEntry_defaultValue, false));
break;
+ case RestrictionEntry.TYPE_BUNDLE:
+ case RestrictionEntry.TYPE_BUNDLE_ARRAY:
+ final int outerDepth = xml.getDepth();
+ List<RestrictionEntry> restrictionEntries = new ArrayList<>();
+ while (XmlUtils.nextElementWithin(xml, outerDepth)) {
+ RestrictionEntry childEntry = loadRestrictionElement(appContext, xml);
+ if (childEntry == null) {
+ Log.w(TAG, "Child entry cannot be loaded for bundle restriction " + key);
+ } else {
+ restrictionEntries.add(childEntry);
+ if (restrictionType == RestrictionEntry.TYPE_BUNDLE_ARRAY
+ && childEntry.getType() != RestrictionEntry.TYPE_BUNDLE) {
+ Log.w(TAG, "bundle_array " + key
+ + " can only contain entries of type bundle");
+ }
+ }
+ }
+ restriction.setRestrictions(restrictionEntries.toArray(new RestrictionEntry[
+ restrictionEntries.size()]));
+ break;
default:
Log.w(TAG, "Unknown restriction type " + restrictionType);
}
return restriction;
}
+
+ /**
+ * Converts a list of restrictions to the corresponding bundle, using the following mapping:
+ * <table>
+ * <tr><th>RestrictionEntry</th><th>Bundle</th></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_BOOLEAN}</td><td>{@link Bundle#putBoolean}</td></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_CHOICE}, {@link RestrictionEntry#TYPE_CHOICE}</td>
+ * <td>{@link Bundle#putStringArray}</td></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_INTEGER}</td><td>{@link Bundle#putInt}</td></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_STRING}</td><td>{@link Bundle#putString}</td></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_BUNDLE}</td><td>{@link Bundle#putBundle}</td></tr>
+ * <tr><td>{@link RestrictionEntry#TYPE_BUNDLE_ARRAY}</td>
+ * <td>{@link Bundle#putParcelableArray}</td></tr>
+ * </table>
+ * @param entries list of restrictions
+ */
+ public static Bundle convertRestrictionsToBundle(List<RestrictionEntry> entries) {
+ final Bundle bundle = new Bundle();
+ for (RestrictionEntry entry : entries) {
+ addRestrictionToBundle(bundle, entry);
+ }
+ return bundle;
+ }
+
+ private static Bundle addRestrictionToBundle(Bundle bundle, RestrictionEntry entry) {
+ switch (entry.getType()) {
+ case RestrictionEntry.TYPE_BOOLEAN:
+ bundle.putBoolean(entry.getKey(), entry.getSelectedState());
+ break;
+ case RestrictionEntry.TYPE_CHOICE:
+ case RestrictionEntry.TYPE_CHOICE_LEVEL:
+ case RestrictionEntry.TYPE_MULTI_SELECT:
+ bundle.putStringArray(entry.getKey(), entry.getAllSelectedStrings());
+ break;
+ case RestrictionEntry.TYPE_INTEGER:
+ bundle.putInt(entry.getKey(), entry.getIntValue());
+ break;
+ case RestrictionEntry.TYPE_STRING:
+ case RestrictionEntry.TYPE_NULL:
+ bundle.putString(entry.getKey(), entry.getSelectedString());
+ break;
+ case RestrictionEntry.TYPE_BUNDLE:
+ RestrictionEntry[] restrictions = entry.getRestrictions();
+ Bundle childBundle = convertRestrictionsToBundle(Arrays.asList(restrictions));
+ bundle.putBundle(entry.getKey(), childBundle);
+ break;
+ case RestrictionEntry.TYPE_BUNDLE_ARRAY:
+ restrictions = entry.getRestrictions();
+ Bundle[] bundleArray = new Bundle[restrictions.length];
+ for (int i = 0; i < restrictions.length; i++) {
+ bundleArray[i] = addRestrictionToBundle(new Bundle(), restrictions[i]);
+ }
+ bundle.putParcelableArray(entry.getKey(), bundleArray);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported restrictionEntry type: " + entry.getType());
+ }
+ return bundle;
+ }
+
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 8f17845..5bdb7bb 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -340,8 +340,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* cleartext network traffic, in which case platform components (e.g., HTTP stacks,
* {@code WebView}, {@code MediaPlayer}) will refuse app's requests to use cleartext traffic.
* Third-party libraries are encouraged to honor this flag as well.
- *
- * @hide
*/
public static final int FLAG_USES_CLEARTEXT_TRAFFIC = 1<<27;
@@ -379,7 +377,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* {@link #FLAG_LARGE_HEAP}, {@link #FLAG_STOPPED},
* {@link #FLAG_SUPPORTS_RTL}, {@link #FLAG_INSTALLED},
* {@link #FLAG_IS_DATA_ONLY}, {@link #FLAG_IS_GAME},
- * {@link #FLAG_FULL_BACKUP_ONLY}, {@link #FLAG_MULTIARCH}.
+ * {@link #FLAG_FULL_BACKUP_ONLY}, {@link #FLAG_USES_CLEARTEXT_TRAFFIC},
+ * {@link #FLAG_MULTIARCH}.
*/
public int flags = 0;
@@ -422,6 +421,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public static final int PRIVATE_FLAG_PRIVILEGED = 1<<3;
/**
+ * Value for {@link #flags}: {@code true} if the application has any IntentFiler with some
+ * data URI using HTTP or HTTPS with an associated VIEW action.
+ *
+ * {@hide}
+ */
+ public static final int PRIVATE_FLAG_HAS_DOMAIN_URLS = 1<<4;
+
+ /**
* Private/hidden flags. See {@code PRIVATE_FLAG_...} constants.
* {@hide}
*/
@@ -452,6 +459,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
public int largestWidthLimitDp = 0;
/** {@hide} */
+ public String volumeUuid;
+ /** {@hide} */
public String scanSourceDir;
/** {@hide} */
public String scanPublicSourceDir;
@@ -655,7 +664,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
}
pw.println(prefix + "dataDir=" + dataDir);
if (sharedLibraryFiles != null) {
- pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles);
+ pw.println(prefix + "sharedLibraryFiles=" + Arrays.toString(sharedLibraryFiles));
}
pw.println(prefix + "enabled=" + enabled + " targetSdkVersion=" + targetSdkVersion
+ " versionCode=" + versionCode);
@@ -719,6 +728,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
requiresSmallestWidthDp = orig.requiresSmallestWidthDp;
compatibleWidthLimitDp = orig.compatibleWidthLimitDp;
largestWidthLimitDp = orig.largestWidthLimitDp;
+ volumeUuid = orig.volumeUuid;
scanSourceDir = orig.scanSourceDir;
scanPublicSourceDir = orig.scanPublicSourceDir;
sourceDir = orig.sourceDir;
@@ -771,6 +781,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(requiresSmallestWidthDp);
dest.writeInt(compatibleWidthLimitDp);
dest.writeInt(largestWidthLimitDp);
+ dest.writeString(volumeUuid);
dest.writeString(scanSourceDir);
dest.writeString(scanPublicSourceDir);
dest.writeString(sourceDir);
@@ -822,6 +833,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
requiresSmallestWidthDp = source.readInt();
compatibleWidthLimitDp = source.readInt();
largestWidthLimitDp = source.readInt();
+ volumeUuid = source.readString();
scanSourceDir = source.readString();
scanPublicSourceDir = source.readString();
sourceDir = source.readString();
@@ -914,6 +926,20 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
/**
* @hide
*/
+ public boolean isSystemApp() {
+ return (flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isUpdatedSystemApp() {
+ return (flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+ }
+
+ /**
+ * @hide
+ */
@Override protected ApplicationInfo getApplicationInfo() {
return this;
}
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index ba62cd6..154ff85 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -44,7 +44,8 @@ interface IPackageInstaller {
void registerCallback(IPackageInstallerCallback callback, int userId);
void unregisterCallback(IPackageInstallerCallback callback);
- void uninstall(String packageName, int flags, in IntentSender statusReceiver, int userId);
+ void uninstall(String packageName, String callerPackageName, int flags,
+ in IntentSender statusReceiver, int userId);
void setPermissionsResult(int sessionId, boolean accepted);
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c6d97f1..55c990f 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -31,6 +31,7 @@ import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
+import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.InstrumentationInfo;
import android.content.pm.KeySet;
import android.content.pm.PackageInfo;
@@ -268,6 +269,12 @@ interface IPackageManager {
void clearCrossProfileIntentFilters(int sourceUserId, String ownerPackage);
/**
+ * Backup/restore support - only the system uid may use these.
+ */
+ byte[] getPreferredActivityBackup(int userId);
+ void restorePreferredActivities(in byte[] backup, int userId);
+
+ /**
* Report the set of 'Home' activity candidates, plus (if any) which of them
* is the current "always use this one" setting.
*/
@@ -425,7 +432,8 @@ interface IPackageManager {
PackageCleanItem nextPackageToClean(in PackageCleanItem lastPackage);
void movePackage(String packageName, IPackageMoveObserver observer, int flags);
-
+ void movePackageAndData(String packageName, String volumeUuid, IPackageMoveObserver observer);
+
boolean addPermissionAsync(in PermissionInfo info);
boolean setInstallLocation(int loc);
@@ -436,6 +444,12 @@ interface IPackageManager {
void verifyPendingInstall(int id, int verificationCode);
void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay);
+ void verifyIntentFilter(int id, int verificationCode, in List<String> failedDomains);
+ int getIntentVerificationStatus(String packageName, int userId);
+ boolean updateIntentVerificationStatus(String packageName, int status, int userId);
+ List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName);
+ List<IntentFilter> getAllIntentFilters(String packageName);
+
VerifierDeviceIdentity getVerifierDeviceIdentity();
boolean isFirstBoot();
diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.aidl b/core/java/android/content/pm/IntentFilterVerificationInfo.aidl
new file mode 100644
index 0000000..00220e5
--- /dev/null
+++ b/core/java/android/content/pm/IntentFilterVerificationInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable IntentFilterVerificationInfo;
diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java
new file mode 100644
index 0000000..28cbaa8
--- /dev/null
+++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * The {@link com.android.server.pm.PackageManagerService} maintains some
+ * {@link IntentFilterVerificationInfo}s for each domain / package / class name per user.
+ *
+ * @hide
+ */
+public final class IntentFilterVerificationInfo implements Parcelable {
+ private static final String TAG = IntentFilterVerificationInfo.class.getName();
+
+ private static final String TAG_DOMAIN = "domain";
+ private static final String ATTR_DOMAIN_NAME = "name";
+ private static final String ATTR_PACKAGE_NAME = "packageName";
+ private static final String ATTR_STATUS = "status";
+
+ private ArrayList<String> mDomains;
+ private String mPackageName;
+ private int mMainStatus;
+
+ public IntentFilterVerificationInfo() {
+ mPackageName = null;
+ mDomains = new ArrayList<>();
+ mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+
+ public IntentFilterVerificationInfo(String packageName, ArrayList<String> domains) {
+ mPackageName = packageName;
+ mDomains = domains;
+ mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+
+ public IntentFilterVerificationInfo(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ readFromXml(parser);
+ }
+
+ public IntentFilterVerificationInfo(Parcel source) {
+ readFromParcel(source);
+ }
+
+ public ArrayList<String> getDomains() {
+ return mDomains;
+ }
+
+ public ArraySet<String> getDomainsSet() {
+ return new ArraySet<>(mDomains);
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public int getStatus() {
+ return mMainStatus;
+ }
+
+ public void setStatus(int s) {
+ if (s >= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED &&
+ s <= INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER) {
+ mMainStatus = s;
+ } else {
+ Log.w(TAG, "Trying to set a non supported status: " + s);
+ }
+ }
+
+ public String getDomainsString() {
+ StringBuilder sb = new StringBuilder();
+ for (String str : mDomains) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ sb.append(str);
+ }
+ return sb.toString();
+ }
+
+ String getStringFromXml(XmlPullParser parser, String attribute, String defaultValue) {
+ String value = parser.getAttributeValue(null, attribute);
+ if (value == null) {
+ String msg = "Missing element under " + TAG +": " + attribute + " at " +
+ parser.getPositionDescription();
+ Log.w(TAG, msg);
+ return defaultValue;
+ } else {
+ return value;
+ }
+ }
+
+ int getIntFromXml(XmlPullParser parser, String attribute, int defaultValue) {
+ String value = parser.getAttributeValue(null, attribute);
+ if (TextUtils.isEmpty(value)) {
+ String msg = "Missing element under " + TAG +": " + attribute + " at " +
+ parser.getPositionDescription();
+ Log.w(TAG, msg);
+ return defaultValue;
+ } else {
+ return Integer.parseInt(value);
+ }
+ }
+
+ public void readFromXml(XmlPullParser parser) throws XmlPullParserException,
+ IOException {
+ mPackageName = getStringFromXml(parser, ATTR_PACKAGE_NAME, null);
+ if (mPackageName == null) {
+ Log.e(TAG, "Package name cannot be null!");
+ }
+ int status = getIntFromXml(parser, ATTR_STATUS, -1);
+ if (status == -1) {
+ Log.e(TAG, "Unknown status value: " + status);
+ }
+ mMainStatus = status;
+
+ mDomains = new ArrayList<>();
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG
+ || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_DOMAIN)) {
+ String name = getStringFromXml(parser, ATTR_DOMAIN_NAME, null);
+ if (!TextUtils.isEmpty(name)) {
+ mDomains.add(name);
+ }
+ } else {
+ Log.w(TAG, "Unknown tag parsing IntentFilter: " + tagName);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ public void writeToXml(XmlSerializer serializer) throws IOException {
+ serializer.attribute(null, ATTR_PACKAGE_NAME, mPackageName);
+ serializer.attribute(null, ATTR_STATUS, String.valueOf(mMainStatus));
+ for (String str : mDomains) {
+ serializer.startTag(null, TAG_DOMAIN);
+ serializer.attribute(null, ATTR_DOMAIN_NAME, str);
+ serializer.endTag(null, TAG_DOMAIN);
+ }
+ }
+
+ public String getStatusString() {
+ return getStatusStringFromValue(mMainStatus);
+ }
+
+ public static String getStatusStringFromValue(int val) {
+ switch (val) {
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK : return "ask";
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS : return "always";
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER : return "never";
+ default:
+ case INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED : return "undefined";
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private void readFromParcel(Parcel source) {
+ mPackageName = source.readString();
+ mMainStatus = source.readInt();
+ mDomains = new ArrayList<>();
+ source.readStringList(mDomains);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mPackageName);
+ dest.writeInt(mMainStatus);
+ dest.writeStringList(mDomains);
+ }
+
+ public static final Creator<IntentFilterVerificationInfo> CREATOR =
+ new Creator<IntentFilterVerificationInfo>() {
+ public IntentFilterVerificationInfo createFromParcel(Parcel source) {
+ return new IntentFilterVerificationInfo(source);
+ }
+ public IntentFilterVerificationInfo[] newArray(int size) {
+ return new IntentFilterVerificationInfo[size];
+ }
+ };
+
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 80efd0b..b7ee82d 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -423,7 +423,7 @@ public class PackageInstaller {
*/
public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) {
try {
- mInstaller.uninstall(packageName, 0, statusReceiver, mUserId);
+ mInstaller.uninstall(packageName, mInstallerPackageName, 0, statusReceiver, mUserId);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
@@ -887,6 +887,8 @@ public class PackageInstaller {
public Uri referrerUri;
/** {@hide} */
public String abiOverride;
+ /** {@hide} */
+ public String volumeUuid;
/**
* Construct parameters for a new package install session.
@@ -911,6 +913,7 @@ public class PackageInstaller {
originatingUri = source.readParcelable(null);
referrerUri = source.readParcelable(null);
abiOverride = source.readString();
+ volumeUuid = source.readString();
}
/**
@@ -1008,6 +1011,7 @@ public class PackageInstaller {
pw.printPair("originatingUri", originatingUri);
pw.printPair("referrerUri", referrerUri);
pw.printPair("abiOverride", abiOverride);
+ pw.printPair("volumeUuid", volumeUuid);
pw.println();
}
@@ -1028,6 +1032,7 @@ public class PackageInstaller {
dest.writeParcelable(originatingUri, flags);
dest.writeParcelable(referrerUri, flags);
dest.writeString(abiOverride);
+ dest.writeString(volumeUuid);
}
public static final Parcelable.Creator<SessionParams>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 59a16da..303b709 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -43,7 +43,9 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.storage.VolumeInfo;
import android.util.AndroidException;
+
import com.android.internal.util.ArrayUtils;
import java.io.File;
@@ -339,15 +341,17 @@ public abstract class PackageManager {
public static final int INSTALL_ALLOW_TEST = 0x00000004;
/**
- * Flag parameter for {@link #installPackage} to indicate that this
- * package has to be installed on the sdcard.
+ * Flag parameter for {@link #installPackage} to indicate that this package
+ * must be installed to an ASEC on a {@link VolumeInfo#TYPE_PUBLIC}.
+ *
* @hide
*/
public static final int INSTALL_EXTERNAL = 0x00000008;
/**
* Flag parameter for {@link #installPackage} to indicate that this package
- * has to be installed on the sdcard.
+ * must be installed to internal storage.
+ *
* @hide
*/
public static final int INSTALL_INTERNAL = 0x00000010;
@@ -969,6 +973,60 @@ public abstract class PackageManager {
public static final int VERIFICATION_REJECT = -1;
/**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManager#verifyIntentFilter} to indicate that the calling
+ * IntentFilter Verifier confirms that the IntentFilter is verified.
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1;
+
+ /**
+ * Used as the {@code verificationCode} argument for
+ * {@link PackageManager#verifyIntentFilter} to indicate that the calling
+ * IntentFilter Verifier confirms that the IntentFilter is NOT verified.
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_VERIFICATION_FAILURE = -1;
+
+ /**
+ * Internal status code to indicate that an IntentFilter verification result is not specified.
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED = 0;
+
+ /**
+ * Used as the {@code status} argument for {@link PackageManager#updateIntentVerificationStatus}
+ * to indicate that the User will always be prompted the Intent Disambiguation Dialog if there
+ * are two or more Intent resolved for the IntentFilter's domain(s).
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK = 1;
+
+ /**
+ * Used as the {@code status} argument for {@link PackageManager#updateIntentVerificationStatus}
+ * to indicate that the User will never be prompted the Intent Disambiguation Dialog if there
+ * are two or more resolution of the Intent. The default App for the domain(s) specified in the
+ * IntentFilter will also ALWAYS be used.
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS = 2;
+
+ /**
+ * Used as the {@code status} argument for {@link PackageManager#updateIntentVerificationStatus}
+ * to indicate that the User may be prompted the Intent Disambiguation Dialog if there
+ * are two or more Intent resolved. The default App for the domain(s) specified in the
+ * IntentFilter will also NEVER be presented to the User.
+ *
+ * @hide
+ */
+ public static final int INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER = 3;
+
+ /**
* Can be used as the {@code millisecondsToDelay} argument for
* {@link PackageManager#extendVerificationTimeout}. This is the
* maximum time {@code PackageManager} waits for the verification
@@ -1600,6 +1658,12 @@ public abstract class PackageManager {
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_GAMEPAD = "android.hardware.gamepad";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device has a full implementation of the android.media.midi.* APIs.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_MIDI = "android.software.midi";
/**
* Action to external storage service to clean out removed apps.
@@ -1674,8 +1738,52 @@ public abstract class PackageManager {
= "android.content.pm.extra.VERIFICATION_VERSION_CODE";
/**
- * The action used to request that the user approve a grant permissions
- * request from the application.
+ * Extra field name for the ID of a intent filter pending verification. Passed to
+ * an intent filter verifier and is used to call back to
+ * {@link PackageManager#verifyIntentFilter(int, int)}
+ *
+ * @hide
+ */
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_ID
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_ID";
+
+ /**
+ * Extra field name for the scheme used for an intent filter pending verification. Passed to
+ * an intent filter verifier and is used to construct the URI to verify against.
+ *
+ * Usually this is "https"
+ *
+ * @hide
+ */
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_URI_SCHEME
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_URI_SCHEME";
+
+ /**
+ * Extra field name for the host names to be used for an intent filter pending verification.
+ * Passed to an intent filter verifier and is used to construct the URI to verify the
+ * intent filter.
+ *
+ * This is a space delimited list of hosts.
+ *
+ * @hide
+ */
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_HOSTS
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_HOSTS";
+
+ /**
+ * Extra field name for the package name to be used for an intent filter pending verification.
+ * Passed to an intent filter verifier and is used to check the verification responses coming
+ * from the hosts. Each host response will need to include the package name of APK containing
+ * the intent filter.
+ *
+ * @hide
+ */
+ public static final String EXTRA_INTENT_FILTER_VERIFICATION_PACKAGE_NAME
+ = "android.content.pm.extra.INTENT_FILTER_VERIFICATION_PACKAGE_NAME";
+
+ /**
+ * The action used to request that the user approve a permission request
+ * from the application.
*
* @hide
*/
@@ -3455,6 +3563,101 @@ public abstract class PackageManager {
int verificationCodeAtTimeout, long millisecondsToDelay);
/**
+ * Allows a package listening to the
+ * {@link Intent#ACTION_INTENT_FILTER_NEEDS_VERIFICATION intent filter verification
+ * broadcast} to respond to the package manager. The response must include
+ * the {@code verificationCode} which is one of
+ * {@link PackageManager#INTENT_FILTER_VERIFICATION_SUCCESS} or
+ * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}.
+ *
+ * @param verificationId pending package identifier as passed via the
+ * {@link PackageManager#EXTRA_VERIFICATION_ID} Intent extra.
+ * @param verificationCode either {@link PackageManager#INTENT_FILTER_VERIFICATION_SUCCESS}
+ * or {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}.
+ * @param outFailedDomains a list of failed domains if the verificationCode is
+ * {@link PackageManager#INTENT_FILTER_VERIFICATION_FAILURE}, otherwise null;
+ * @throws SecurityException if the caller does not have the
+ * INTENT_FILTER_VERIFICATION_AGENT permission.
+ *
+ * @hide
+ */
+ public abstract void verifyIntentFilter(int verificationId, int verificationCode,
+ List<String> outFailedDomains);
+
+ /**
+ * Get the status of a Domain Verification Result for an IntentFilter. This is
+ * related to the {@link android.content.IntentFilter#setAutoVerify(boolean)} and
+ * {@link android.content.IntentFilter#getAutoVerify()}
+ *
+ * This is used by the ResolverActivity to change the status depending on what the User select
+ * in the Disambiguation Dialog and also used by the Settings App for changing the default App
+ * for a domain.
+ *
+ * @param packageName The package name of the Activity associated with the IntentFilter.
+ * @param userId The user id.
+ *
+ * @return The status to set to. This can be
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED}
+ *
+ * @hide
+ */
+ public abstract int getIntentVerificationStatus(String packageName, int userId);
+
+ /**
+ * Allow to change the status of a Intent Verification status for all IntentFilter of an App.
+ * This is related to the {@link android.content.IntentFilter#setAutoVerify(boolean)} and
+ * {@link android.content.IntentFilter#getAutoVerify()}
+ *
+ * This is used by the ResolverActivity to change the status depending on what the User select
+ * in the Disambiguation Dialog and also used by the Settings App for changing the default App
+ * for a domain.
+ *
+ * @param packageName The package name of the Activity associated with the IntentFilter.
+ * @param status The status to set to. This can be
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS} or
+ * {@link #INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER}
+ * @param userId The user id.
+ *
+ * @return true if the status has been set. False otherwise.
+ *
+ * @hide
+ */
+ public abstract boolean updateIntentVerificationStatus(String packageName, int status,
+ int userId);
+
+ /**
+ * Get the list of IntentFilterVerificationInfo for a specific package and User.
+ *
+ * @param packageName the package name. When this parameter is set to a non null value,
+ * the results will be filtered by the package name provided.
+ * Otherwise, there will be no filtering and it will return a list
+ * corresponding for all packages
+ *
+ * @return a list of IntentFilterVerificationInfo for a specific package.
+ *
+ * @hide
+ */
+ public abstract List<IntentFilterVerificationInfo> getIntentFilterVerifications(
+ String packageName);
+
+ /**
+ * Get the list of IntentFilter for a specific package.
+ *
+ * @param packageName the package name. This parameter is set to a non null value,
+ * the list will contain all the IntentFilter for that package.
+ * Otherwise, the list will be empty.
+ *
+ * @return a list of IntentFilter for a specific package.
+ *
+ * @hide
+ */
+ public abstract List<IntentFilter> getAllIntentFilters(String packageName);
+
+ /**
* Change the installer associated with a given package. There are limitations
* on how the installer package can be changed; in particular:
* <ul>
@@ -3916,8 +4119,12 @@ public abstract class PackageManager {
*
* @hide
*/
- public abstract void movePackage(
- String packageName, IPackageMoveObserver observer, int flags);
+ @Deprecated
+ public abstract void movePackage(String packageName, IPackageMoveObserver observer, int flags);
+
+ /** {@hide} */
+ public abstract void movePackageAndData(String packageName, String volumeUuid,
+ IPackageMoveObserver observer);
/**
* Returns the device identity that verifiers can use to associate their scheme to a particular
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 212cf6d..c1e6a4d 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -604,7 +604,7 @@ public class PackageParser {
public final static int PARSE_MUST_BE_APK = 1<<2;
public final static int PARSE_IGNORE_PROCESSES = 1<<3;
public final static int PARSE_FORWARD_LOCK = 1<<4;
- public final static int PARSE_ON_SDCARD = 1<<5;
+ public final static int PARSE_EXTERNAL_STORAGE = 1<<5;
public final static int PARSE_IS_SYSTEM_DIR = 1<<6;
public final static int PARSE_IS_PRIVILEGED = 1<<7;
public final static int PARSE_COLLECT_CERTIFICATES = 1<<8;
@@ -1408,7 +1408,7 @@ public class PackageParser {
}
/* Set the global "on SD card" flag */
- if ((flags & PARSE_ON_SDCARD) != 0) {
+ if ((flags & PARSE_EXTERNAL_STORAGE) != 0) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE;
}
@@ -2750,15 +2750,51 @@ public class PackageParser {
}
}
- addSharedLibrariesForBackwardCompatibility(owner);
+ modifySharedLibrariesForBackwardCompatibility(owner);
+
+ if (hasDomainURLs(owner)) {
+ owner.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
+ } else {
+ owner.applicationInfo.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS;
+ }
return true;
}
- private static void addSharedLibrariesForBackwardCompatibility(Package owner) {
- if (owner.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
- owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, "org.apache.http.legacy");
+ private static void modifySharedLibrariesForBackwardCompatibility(Package owner) {
+ // "org.apache.http.legacy" is now a part of the boot classpath so it doesn't need
+ // to be an explicit dependency.
+ //
+ // A future change will remove this library from the boot classpath, at which point
+ // all apps that target SDK 21 and earlier will have it automatically added to their
+ // dependency lists.
+ owner.usesLibraries = ArrayUtils.remove(owner.usesLibraries, "org.apache.http.legacy");
+ owner.usesOptionalLibraries = ArrayUtils.remove(owner.usesOptionalLibraries,
+ "org.apache.http.legacy");
+ }
+
+ /**
+ * Check if one of the IntentFilter as an action VIEW and a HTTP/HTTPS data URI
+ */
+ private static boolean hasDomainURLs(Package pkg) {
+ if (pkg == null || pkg.activities == null) return false;
+ final ArrayList<Activity> activities = pkg.activities;
+ final int countActivities = activities.size();
+ for (int n=0; n<countActivities; n++) {
+ Activity activity = activities.get(n);
+ ArrayList<ActivityIntentInfo> filters = activity.intents;
+ if (filters == null) continue;
+ final int countFilters = filters.size();
+ for (int m=0; m<countFilters; m++) {
+ ActivityIntentInfo aii = filters.get(m);
+ if (!aii.hasAction(Intent.ACTION_VIEW)) continue;
+ if (aii.hasDataScheme(IntentFilter.SCHEME_HTTP) ||
+ aii.hasDataScheme(IntentFilter.SCHEME_HTTPS)) {
+ return true;
+ }
+ }
}
+ return false;
}
/**
@@ -3149,7 +3185,7 @@ public class PackageParser {
if (parser.getName().equals("intent-filter")) {
ActivityIntentInfo intent = new ActivityIntentInfo(a);
- if (!parseIntent(res, parser, attrs, true, intent, outError)) {
+ if (!parseIntent(res, parser, attrs, true, true, intent, outError)) {
return null;
}
if (intent.countActions() == 0) {
@@ -3161,7 +3197,7 @@ public class PackageParser {
}
} else if (!receiver && parser.getName().equals("preferred")) {
ActivityIntentInfo intent = new ActivityIntentInfo(a);
- if (!parseIntent(res, parser, attrs, false, intent, outError)) {
+ if (!parseIntent(res, parser, attrs, false, false, intent, outError)) {
return null;
}
if (intent.countActions() == 0) {
@@ -3341,7 +3377,7 @@ public class PackageParser {
if (parser.getName().equals("intent-filter")) {
ActivityIntentInfo intent = new ActivityIntentInfo(a);
- if (!parseIntent(res, parser, attrs, true, intent, outError)) {
+ if (!parseIntent(res, parser, attrs, true, true, intent, outError)) {
return null;
}
if (intent.countActions() == 0) {
@@ -3521,7 +3557,7 @@ public class PackageParser {
if (parser.getName().equals("intent-filter")) {
ProviderIntentInfo intent = new ProviderIntentInfo(outInfo);
- if (!parseIntent(res, parser, attrs, true, intent, outError)) {
+ if (!parseIntent(res, parser, attrs, true, false, intent, outError)) {
return false;
}
outInfo.intents.add(intent);
@@ -3780,7 +3816,7 @@ public class PackageParser {
if (parser.getName().equals("intent-filter")) {
ServiceIntentInfo intent = new ServiceIntentInfo(s);
- if (!parseIntent(res, parser, attrs, true, intent, outError)) {
+ if (!parseIntent(res, parser, attrs, true, false, intent, outError)) {
return null;
}
@@ -3981,7 +4017,7 @@ public class PackageParser {
= "http://schemas.android.com/apk/res/android";
private boolean parseIntent(Resources res, XmlPullParser parser, AttributeSet attrs,
- boolean allowGlobs, IntentInfo outInfo, String[] outError)
+ boolean allowGlobs, boolean allowAutoVerify, IntentInfo outInfo, String[] outError)
throws XmlPullParserException, IOException {
TypedArray sa = res.obtainAttributes(attrs,
@@ -4006,6 +4042,12 @@ public class PackageParser {
outInfo.banner = sa.getResourceId(
com.android.internal.R.styleable.AndroidManifestIntentFilter_banner, 0);
+ if (allowAutoVerify) {
+ outInfo.setAutoVerify(sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestIntentFilter_autoVerify,
+ false));
+ }
+
sa.recycle();
int outerDepth = parser.getDepth();
@@ -4399,6 +4441,20 @@ public class PackageParser {
return applicationInfo.isForwardLocked();
}
+ /**
+ * @hide
+ */
+ public boolean isSystemApp() {
+ return applicationInfo.isSystemApp();
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isUpdatedSystemApp() {
+ return applicationInfo.isUpdatedSystemApp();
+ }
+
public String toString() {
return "Package{"
+ Integer.toHexString(System.identityHashCode(this))
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index a9c7be3..92b8055 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -37,10 +37,14 @@ public class PackageUserState {
public ArraySet<String> disabledComponents;
public ArraySet<String> enabledComponents;
+ public int domainVerificationStatus;
+
public PackageUserState() {
installed = true;
hidden = false;
enabled = COMPONENT_ENABLED_STATE_DEFAULT;
+ domainVerificationStatus =
+ PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
}
public PackageUserState(PackageUserState o) {
@@ -51,9 +55,10 @@ public class PackageUserState {
hidden = o.hidden;
lastDisableAppCaller = o.lastDisableAppCaller;
disabledComponents = o.disabledComponents != null
- ? new ArraySet<String>(o.disabledComponents) : null;
+ ? new ArraySet<>(o.disabledComponents) : null;
enabledComponents = o.enabledComponents != null
- ? new ArraySet<String>(o.enabledComponents) : null;
+ ? new ArraySet<>(o.enabledComponents) : null;
blockUninstall = o.blockUninstall;
+ domainVerificationStatus = o.domainVerificationStatus;
}
}
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index fe3aec9..7b141f0 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -143,6 +143,11 @@ public class ResolveInfo implements Parcelable {
*/
public boolean system;
+ /**
+ * @hide Does the associated IntentFilter needs verification ?
+ */
+ public boolean filterNeedsVerification;
+
private ComponentInfo getComponentInfo() {
if (activityInfo != null) return activityInfo;
if (serviceInfo != null) return serviceInfo;
@@ -283,6 +288,7 @@ public class ResolveInfo implements Parcelable {
resolvePackageName = orig.resolvePackageName;
system = orig.system;
targetUserId = orig.targetUserId;
+ filterNeedsVerification = orig.filterNeedsVerification;
}
public String toString() {
@@ -344,6 +350,7 @@ public class ResolveInfo implements Parcelable {
dest.writeInt(targetUserId);
dest.writeInt(system ? 1 : 0);
dest.writeInt(noResourceId ? 1 : 0);
+ dest.writeInt(filterNeedsVerification ? 1 : 0);
}
public static final Creator<ResolveInfo> CREATOR
@@ -389,6 +396,7 @@ public class ResolveInfo implements Parcelable {
targetUserId = source.readInt();
system = source.readInt() != 0;
noResourceId = source.readInt() != 0;
+ filterNeedsVerification = source.readInt() != 0;
}
public static class DisplayNameComparator
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 7d8dff3..fdafb04 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -71,10 +71,15 @@ import java.util.Arrays;
*/
public class ColorStateList implements Parcelable {
private static final String TAG = "ColorStateList";
+
private static final int DEFAULT_COLOR = Color.RED;
private static final int[][] EMPTY = new int[][] { new int[0] };
- private static final SparseArray<WeakReference<ColorStateList>> sCache =
- new SparseArray<WeakReference<ColorStateList>>();
+
+ /** Thread-safe cache of single-color ColorStateLists. */
+ private static final SparseArray<WeakReference<ColorStateList>> sCache = new SparseArray<>();
+
+ /** Lazily-created factory for this color state list. */
+ private ColorStateListFactory mFactory;
private int[][] mThemeAttrs;
private int mChangingConfigurations;
@@ -125,7 +130,7 @@ public class ColorStateList implements Parcelable {
}
final ColorStateList csl = new ColorStateList(EMPTY, new int[] { color });
- sCache.put(color, new WeakReference<ColorStateList>(csl));
+ sCache.put(color, new WeakReference<>(csl));
return csl;
}
}
@@ -141,11 +146,13 @@ public class ColorStateList implements Parcelable {
*/
private ColorStateList(ColorStateList orig) {
if (orig != null) {
+ mChangingConfigurations = orig.mChangingConfigurations;
mStateSpecs = orig.mStateSpecs;
mDefaultColor = orig.mDefaultColor;
mIsOpaque = orig.mIsOpaque;
- // Deep copy, this may change due to theming.
+ // Deep copy, these may change due to applyTheme().
+ mThemeAttrs = orig.mThemeAttrs.clone();
mColors = orig.mColors.clone();
}
}
@@ -329,6 +336,7 @@ public class ColorStateList implements Parcelable {
* attributes.
*
* @return whether a theme can be applied to this color state list
+ * @hide only for resource preloading
*/
public boolean canApplyTheme() {
return mThemeAttrs != null;
@@ -336,10 +344,15 @@ public class ColorStateList implements Parcelable {
/**
* Applies a theme to this color state list.
+ * <p>
+ * <strong>Note:</strong> Applying a theme may affect the changing
+ * configuration parameters of this color state list. After calling this
+ * method, any dependent configurations must be updated by obtaining the
+ * new configuration mask from {@link #getChangingConfigurations()}.
*
* @param t the theme to apply
*/
- public void applyTheme(Theme t) {
+ private void applyTheme(Theme t) {
if (mThemeAttrs == null) {
return;
}
@@ -376,6 +389,38 @@ public class ColorStateList implements Parcelable {
onColorsChanged();
}
+ /**
+ * Returns an appropriately themed color state list.
+ *
+ * @param t the theme to apply
+ * @return a copy of the color state list with the theme applied, or the
+ * color state list itself if there were no unresolved theme
+ * attributes
+ * @hide only for resource preloading
+ */
+ public ColorStateList obtainForTheme(Theme t) {
+ if (t == null || !canApplyTheme()) {
+ return this;
+ }
+
+ final ColorStateList clone = new ColorStateList(this);
+ clone.applyTheme(t);
+ return clone;
+ }
+
+ /**
+ * Returns a mask of the configuration parameters for which this color
+ * state list may change, requiring that it be re-created.
+ *
+ * @return a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}
+ *
+ * @see android.content.pm.ActivityInfo
+ */
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
private int modulateColorAlpha(int baseColor, float alphaMod) {
if (alphaMod == 1.0f) {
return baseColor;
@@ -383,8 +428,7 @@ public class ColorStateList implements Parcelable {
final int baseAlpha = Color.alpha(baseColor);
final int alpha = MathUtils.constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255);
- final int color = (baseColor & 0xFFFFFF) | (alpha << 24);
- return color;
+ return (baseColor & 0xFFFFFF) | (alpha << 24);
}
/**
@@ -534,14 +578,18 @@ public class ColorStateList implements Parcelable {
}
/**
- * @return A factory that can create new instances of this ColorStateList.
+ * @return a factory that can create new instances of this ColorStateList
+ * @hide only for resource preloading
*/
- ColorStateListFactory getFactory() {
- return new ColorStateListFactory(this);
+ public ConstantState<ColorStateList> getConstantState() {
+ if (mFactory != null) {
+ mFactory = new ColorStateListFactory(this);
+ }
+ return mFactory;
}
- static class ColorStateListFactory extends ConstantState<ColorStateList> {
- final ColorStateList mSrc;
+ private static class ColorStateListFactory extends ConstantState<ColorStateList> {
+ private final ColorStateList mSrc;
public ColorStateListFactory(ColorStateList src) {
mSrc = src;
@@ -559,13 +607,7 @@ public class ColorStateList implements Parcelable {
@Override
public ColorStateList newInstance(Resources res, Theme theme) {
- if (theme == null || !mSrc.canApplyTheme()) {
- return mSrc;
- }
-
- final ColorStateList clone = new ColorStateList(mSrc);
- clone.applyTheme(theme);
- return clone;
+ return mSrc.obtainForTheme(theme);
}
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 14af584..bc6d4ce 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1284,14 +1284,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
/**
* Set the locale. This is the preferred way for setting up the locale (instead of using the
- * direct accessor). This will also set the userLocale and layout direction according to
- * the locale.
+ * direct accessor). This will also set the layout direction according to the locale.
*
* @param loc The locale. Can be null.
*/
public void setLocale(Locale loc) {
locale = loc;
- userSetLocale = true;
setLayoutDirection(locale);
}
@@ -1314,7 +1312,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* {@link View#LAYOUT_DIRECTION_LTR}. If not null will set it to the layout direction
* corresponding to the Locale.
*
- * @see {@link View#LAYOUT_DIRECTION_LTR} and {@link View#LAYOUT_DIRECTION_RTL}
+ * @see View#LAYOUT_DIRECTION_LTR
+ * @see View#LAYOUT_DIRECTION_RTL
*/
public void setLayoutDirection(Locale locale) {
// There is a "1" difference between the configuration values for
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 95ad57e..334d180 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -16,7 +16,10 @@
package android.content.res;
+import android.annotation.AttrRes;
import android.annotation.ColorInt;
+import android.annotation.StyleRes;
+import android.annotation.StyleableRes;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -41,7 +44,6 @@ import android.annotation.RawRes;
import android.annotation.StringRes;
import android.annotation.XmlRes;
import android.content.pm.ActivityInfo;
-import android.content.res.ColorStateList.ColorStateListFactory;
import android.graphics.Movie;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -111,12 +113,15 @@ public class Resources {
// single-threaded, and after that these are immutable.
private static final LongSparseArray<ConstantState>[] sPreloadedDrawables;
private static final LongSparseArray<ConstantState> sPreloadedColorDrawables
- = new LongSparseArray<ConstantState>();
- private static final LongSparseArray<ColorStateListFactory> sPreloadedColorStateLists
- = new LongSparseArray<ColorStateListFactory>();
+ = new LongSparseArray<>();
+ private static final LongSparseArray<android.content.res.ConstantState<ColorStateList>>
+ sPreloadedColorStateLists = new LongSparseArray<>();
+
+ private static final String CACHE_NOT_THEMED = "";
+ private static final String CACHE_NULL_THEME = "null_theme";
// Pool of TypedArrays targeted to this Resources object.
- final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<TypedArray>(5);
+ final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<>(5);
// Used by BridgeResources in layoutlib
static Resources mSystem = null;
@@ -128,21 +133,19 @@ public class Resources {
private final Object mAccessLock = new Object();
private final Configuration mTmpConfig = new Configuration();
private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mDrawableCache =
- new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
+ new ArrayMap<>();
private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache =
- new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
+ new ArrayMap<>();
private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache =
- new ConfigurationBoundResourceCache<ColorStateList>(this);
+ new ConfigurationBoundResourceCache<>(this);
private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
- new ConfigurationBoundResourceCache<Animator>(this);
+ new ConfigurationBoundResourceCache<>(this);
private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
- new ConfigurationBoundResourceCache<StateListAnimator>(this);
+ new ConfigurationBoundResourceCache<>(this);
private TypedValue mTmpValue = new TypedValue();
private boolean mPreloading;
- private TypedArray mCachedStyledAttributes = null;
-
private int mLastCachedXmlBlockIndex = -1;
private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 };
private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4];
@@ -157,8 +160,8 @@ public class Resources {
static {
sPreloadedDrawables = new LongSparseArray[2];
- sPreloadedDrawables[0] = new LongSparseArray<ConstantState>();
- sPreloadedDrawables[1] = new LongSparseArray<ConstantState>();
+ sPreloadedDrawables[0] = new LongSparseArray<>();
+ sPreloadedDrawables[1] = new LongSparseArray<>();
}
/**
@@ -1475,7 +1478,7 @@ public class Resources {
* @see #obtainStyledAttributes(int, int[])
* @see #obtainStyledAttributes(AttributeSet, int[], int, int)
*/
- public TypedArray obtainStyledAttributes(int[] attrs) {
+ public TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) {
final int len = attrs.length;
final TypedArray array = TypedArray.obtain(Resources.this, len);
array.mTheme = this;
@@ -1503,7 +1506,8 @@ public class Resources {
* @see #obtainStyledAttributes(int[])
* @see #obtainStyledAttributes(AttributeSet, int[], int, int)
*/
- public TypedArray obtainStyledAttributes(int resid, int[] attrs) throws NotFoundException {
+ public TypedArray obtainStyledAttributes(@StyleRes int resid, @StyleableRes int[] attrs)
+ throws NotFoundException {
final int len = attrs.length;
final TypedArray array = TypedArray.obtain(Resources.this, len);
array.mTheme = this;
@@ -1586,7 +1590,7 @@ public class Resources {
* @see #obtainStyledAttributes(int, int[])
*/
public TypedArray obtainStyledAttributes(AttributeSet set,
- int[] attrs, int defStyleAttr, int defStyleRes) {
+ @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
final int len = attrs.length;
final TypedArray array = TypedArray.obtain(Resources.this, len);
@@ -1876,7 +1880,7 @@ public class Resources {
// the framework.
mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
- int configChanges = calcConfigChanges(config);
+ final int configChanges = calcConfigChanges(config);
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
mConfiguration.setLayoutDirection(mConfiguration.locale);
@@ -1891,7 +1895,8 @@ public class Resources {
if (mConfiguration.locale != null) {
locale = adjustLanguageTag(mConfiguration.locale.toLanguageTag());
}
- int width, height;
+
+ final int width, height;
if (mMetrics.widthPixels >= mMetrics.heightPixels) {
width = mMetrics.widthPixels;
height = mMetrics.heightPixels;
@@ -1901,12 +1906,15 @@ public class Resources {
//noinspection SuspiciousNameCombination
height = mMetrics.widthPixels;
}
- int keyboardHidden = mConfiguration.keyboardHidden;
- if (keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
- && mConfiguration.hardKeyboardHidden
- == Configuration.HARDKEYBOARDHIDDEN_YES) {
+
+ final int keyboardHidden;
+ if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
+ && mConfiguration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
+ } else {
+ keyboardHidden = mConfiguration.keyboardHidden;
}
+
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
locale, mConfiguration.orientation,
mConfiguration.touchscreen,
@@ -2436,8 +2444,8 @@ public class Resources {
}
}
- // Next, check preloaded drawables. These are unthemed but may have
- // themeable attributes.
+ // Next, check preloaded drawables. These may contain unresolved theme
+ // attributes.
final ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
@@ -2445,42 +2453,49 @@ public class Resources {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
- final Drawable dr;
+ Drawable dr;
if (cs != null) {
- final Drawable clonedDr = cs.newDrawable(this);
- if (theme != null) {
- dr = clonedDr.mutate();
- dr.applyTheme(theme);
- dr.clearMutated();
- } else {
- dr = clonedDr;
- }
+ dr = cs.newDrawable(this);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
- dr = loadDrawableForCookie(value, id, theme);
+ dr = loadDrawableForCookie(value, id, null);
+ }
+
+ // Determine if the drawable has unresolved theme attributes. If it
+ // does, we'll need to apply a theme and store it in a theme-specific
+ // cache.
+ final String cacheKey;
+ if (!dr.canApplyTheme()) {
+ cacheKey = CACHE_NOT_THEMED;
+ } else if (theme == null) {
+ cacheKey = CACHE_NULL_THEME;
+ } else {
+ cacheKey = theme.getKey();
+ dr = dr.mutate();
+ dr.applyTheme(theme);
+ dr.clearMutated();
}
// If we were able to obtain a drawable, store it in the appropriate
- // cache (either preload or themed).
+ // cache: preload, not themed, null theme, or theme-specific.
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
- cacheDrawable(value, theme, isColorDrawable, caches, key, dr);
+ cacheDrawable(value, isColorDrawable, caches, cacheKey, key, dr);
}
return dr;
}
- private void cacheDrawable(TypedValue value, Theme theme, boolean isColorDrawable,
+ private void cacheDrawable(TypedValue value, boolean isColorDrawable,
ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
- long key, Drawable dr) {
+ String cacheKey, long key, Drawable dr) {
final ConstantState cs = dr.getConstantState();
if (cs == null) {
return;
}
if (mPreloading) {
- // Preloaded drawables never have a theme, but may be themeable.
final int changingConfigs = cs.getChangingConfigurations();
if (isColorDrawable) {
if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) {
@@ -2502,16 +2517,15 @@ public class Resources {
}
} else {
synchronized (mAccessLock) {
- final String themeKey = theme == null ? "" : theme.mKey;
- LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey);
+ LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(cacheKey);
if (themedCache == null) {
// Clean out the caches before we add more. This shouldn't
// happen very often.
pruneCaches(caches);
- themedCache = new LongSparseArray<WeakReference<ConstantState>>(1);
- caches.put(themeKey, themedCache);
+ themedCache = new LongSparseArray<>(1);
+ caches.put(cacheKey, themedCache);
}
- themedCache.put(key, new WeakReference<ConstantState>(cs));
+ themedCache.put(key, new WeakReference<>(cs));
}
}
}
@@ -2607,46 +2621,47 @@ public class Resources {
ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
long key, Theme theme) {
synchronized (mAccessLock) {
- final String themeKey = theme != null ? theme.mKey : "";
- final LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey);
- if (themedCache != null) {
- final Drawable themedDrawable = getCachedDrawableLocked(themedCache, key);
- if (themedDrawable != null) {
- return themedDrawable;
- }
+ // First search theme-agnostic cache.
+ final Drawable unthemedDrawable = getCachedDrawableLocked(
+ caches, key, CACHE_NOT_THEMED);
+ if (unthemedDrawable != null) {
+ return unthemedDrawable;
}
- // No cached drawable, we'll need to create a new one.
- return null;
+ // Next search theme-specific cache.
+ final String themeKey = theme != null ? theme.getKey() : CACHE_NULL_THEME;
+ return getCachedDrawableLocked(caches, key, themeKey);
}
}
+ private Drawable getCachedDrawableLocked(
+ ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
+ long key, String themeKey) {
+ final LongSparseArray<WeakReference<ConstantState>> cache = caches.get(themeKey);
+ if (cache != null) {
+ final ConstantState entry = getConstantStateLocked(cache, key);
+ if (entry != null) {
+ return entry.newDrawable(this);
+ }
+ }
+ return null;
+ }
+
private ConstantState getConstantStateLocked(
LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) {
final WeakReference<ConstantState> wr = drawableCache.get(key);
- if (wr != null) { // we have the key
+ if (wr != null) {
final ConstantState entry = wr.get();
if (entry != null) {
- //Log.i(TAG, "Returning cached drawable @ #" +
- // Integer.toHexString(((Integer)key).intValue())
- // + " in " + this + ": " + entry);
return entry;
- } else { // our entry has been purged
+ } else {
+ // Our entry has been purged.
drawableCache.delete(key);
}
}
return null;
}
- private Drawable getCachedDrawableLocked(
- LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) {
- final ConstantState entry = getConstantStateLocked(drawableCache, key);
- if (entry != null) {
- return entry.newDrawable(this);
- }
- return null;
- }
-
@Nullable
ColorStateList loadColorStateList(TypedValue value, int id, Theme theme)
throws NotFoundException {
@@ -2665,7 +2680,8 @@ public class Resources {
// Handle inline color definitions.
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
&& value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
- final ColorStateListFactory factory = sPreloadedColorStateLists.get(key);
+ final android.content.res.ConstantState<ColorStateList> factory =
+ sPreloadedColorStateLists.get(key);
if (factory != null) {
return factory.newInstance();
}
@@ -2675,7 +2691,7 @@ public class Resources {
if (mPreloading) {
if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
"color")) {
- sPreloadedColorStateLists.put(key, csl.getFactory());
+ sPreloadedColorStateLists.put(key, csl.getConstantState());
}
}
@@ -2689,7 +2705,8 @@ public class Resources {
return csl;
}
- final ColorStateListFactory factory = sPreloadedColorStateLists.get(key);
+ final android.content.res.ConstantState<ColorStateList> factory =
+ sPreloadedColorStateLists.get(key);
if (factory != null) {
csl = factory.newInstance(this, theme);
}
@@ -2702,10 +2719,10 @@ public class Resources {
if (mPreloading) {
if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
"color")) {
- sPreloadedColorStateLists.put(key, csl.getFactory());
+ sPreloadedColorStateLists.put(key, csl.getConstantState());
}
} else {
- cache.put(key, theme, csl.getFactory());
+ cache.put(key, theme, csl.getConstantState());
}
}
@@ -2830,15 +2847,6 @@ public class Resources {
+ Integer.toHexString(id));
}
- /*package*/ void recycleCachedStyledAttributes(TypedArray attrs) {
- synchronized (mAccessLock) {
- final TypedArray cached = mCachedStyledAttributes;
- if (cached == null || cached.mData.length < attrs.mData.length) {
- mCachedStyledAttributes = attrs;
- }
- }
- }
-
/**
* Obtains styled attributes from the theme, if available, or unstyled
* resources if the theme is null.
diff --git a/core/java/android/database/DatabaseErrorHandler.java b/core/java/android/database/DatabaseErrorHandler.java
index f0c5452..55ad921 100644
--- a/core/java/android/database/DatabaseErrorHandler.java
+++ b/core/java/android/database/DatabaseErrorHandler.java
@@ -19,13 +19,12 @@ package android.database;
import android.database.sqlite.SQLiteDatabase;
/**
- * An interface to let the apps define the actions to take when the following errors are detected
- * database corruption
+ * An interface to let apps define an action to take when database corruption is detected.
*/
public interface DatabaseErrorHandler {
/**
- * defines the method to be invoked when database corruption is detected.
+ * The method invoked when database corruption is detected.
* @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
* is detected.
*/
diff --git a/core/java/android/database/DefaultDatabaseErrorHandler.java b/core/java/android/database/DefaultDatabaseErrorHandler.java
index b234e34..7fa2b40 100755
--- a/core/java/android/database/DefaultDatabaseErrorHandler.java
+++ b/core/java/android/database/DefaultDatabaseErrorHandler.java
@@ -24,7 +24,7 @@ import android.util.Log;
import android.util.Pair;
/**
- * Default class used to define the actions to take when the database corruption is reported
+ * Default class used to define the action to take when database corruption is reported
* by sqlite.
* <p>
* An application can specify an implementation of {@link DatabaseErrorHandler} on the
@@ -38,7 +38,7 @@ import android.util.Pair;
* The specified {@link DatabaseErrorHandler} is used to handle database corruption errors, if they
* occur.
* <p>
- * If null is specified for DatabaeErrorHandler param in the above calls, then this class is used
+ * If null is specified for the DatabaseErrorHandler param in the above calls, this class is used
* as the default {@link DatabaseErrorHandler}.
*/
public final class DefaultDatabaseErrorHandler implements DatabaseErrorHandler {
diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl
index d5dfaf6..9bc2f46 100644
--- a/core/java/android/hardware/ICameraService.aidl
+++ b/core/java/android/hardware/ICameraService.aidl
@@ -75,4 +75,11 @@ interface ICameraService
out BinderHolder device);
int setTorchMode(String CameraId, boolean enabled, IBinder clientBinder);
+
+ /**
+ * Notify the camera service of a system event. Should only be called from system_server.
+ *
+ * Callers require the android.permission.CAMERA_SEND_SYSTEM_EVENTS permission.
+ */
+ oneway void notifySystemEvent(int eventId, int arg0);
}
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index a6c3ea4..88fa339 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -54,11 +54,13 @@ public class SystemSensorManager extends SensorManager {
// Looper associated with the context in which this instance was created.
private final Looper mMainLooper;
private final int mTargetSdkLevel;
+ private final String mPackageName;
/** {@hide} */
public SystemSensorManager(Context context, Looper mainLooper) {
mMainLooper = mainLooper;
mTargetSdkLevel = context.getApplicationInfo().targetSdkVersion;
+ mPackageName = context.getPackageName();
synchronized(sSensorModuleLock) {
if (!sSensorModuleInitialized) {
sSensorModuleInitialized = true;
@@ -117,14 +119,14 @@ public class SystemSensorManager extends SensorManager {
if (queue == null) {
Looper looper = (handler != null) ? handler.getLooper() : mMainLooper;
queue = new SensorEventQueue(listener, looper, this);
- if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags)) {
+ if (!queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs)) {
queue.dispose();
return false;
}
mSensorListeners.put(listener, queue);
return true;
} else {
- return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags);
+ return queue.addSensor(sensor, delayUs, maxBatchReportLatencyUs);
}
}
}
@@ -165,14 +167,14 @@ public class SystemSensorManager extends SensorManager {
TriggerEventQueue queue = mTriggerListeners.get(listener);
if (queue == null) {
queue = new TriggerEventQueue(listener, mMainLooper, this);
- if (!queue.addSensor(sensor, 0, 0, 0)) {
+ if (!queue.addSensor(sensor, 0, 0)) {
queue.dispose();
return false;
}
mTriggerListeners.put(listener, queue);
return true;
} else {
- return queue.addSensor(sensor, 0, 0, 0);
+ return queue.addSensor(sensor, 0, 0);
}
}
}
@@ -223,9 +225,9 @@ public class SystemSensorManager extends SensorManager {
*/
private static abstract class BaseEventQueue {
private native long nativeInitBaseEventQueue(BaseEventQueue eventQ, MessageQueue msgQ,
- float[] scratch);
+ float[] scratch, String packageName);
private static native int nativeEnableSensor(long eventQ, int handle, int rateUs,
- int maxBatchReportLatencyUs, int reservedFlags);
+ int maxBatchReportLatencyUs);
private static native int nativeDisableSensor(long eventQ, int handle);
private static native void nativeDestroySensorEventQueue(long eventQ);
private static native int nativeFlushSensor(long eventQ);
@@ -238,7 +240,8 @@ public class SystemSensorManager extends SensorManager {
protected final SystemSensorManager mManager;
BaseEventQueue(Looper looper, SystemSensorManager manager) {
- nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mScratch);
+ nSensorEventQueue = nativeInitBaseEventQueue(this, looper.getQueue(), mScratch,
+ manager.mPackageName);
mCloseGuard.open("dispose");
mManager = manager;
}
@@ -248,7 +251,7 @@ public class SystemSensorManager extends SensorManager {
}
public boolean addSensor(
- Sensor sensor, int delayUs, int maxBatchReportLatencyUs, int reservedFlags) {
+ Sensor sensor, int delayUs, int maxBatchReportLatencyUs) {
// Check if already present.
int handle = sensor.getHandle();
if (mActiveSensors.get(handle)) return false;
@@ -256,10 +259,10 @@ public class SystemSensorManager extends SensorManager {
// Get ready to receive events before calling enable.
mActiveSensors.put(handle, true);
addSensorEvent(sensor);
- if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs, reservedFlags) != 0) {
+ if (enableSensor(sensor, delayUs, maxBatchReportLatencyUs) != 0) {
// Try continuous mode if batching fails.
if (maxBatchReportLatencyUs == 0 ||
- maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0, 0) != 0) {
+ maxBatchReportLatencyUs > 0 && enableSensor(sensor, delayUs, 0) != 0) {
removeSensor(sensor, false);
return false;
}
@@ -328,11 +331,11 @@ public class SystemSensorManager extends SensorManager {
}
private int enableSensor(
- Sensor sensor, int rateUs, int maxBatchReportLatencyUs, int reservedFlags) {
+ Sensor sensor, int rateUs, int maxBatchReportLatencyUs) {
if (nSensorEventQueue == 0) throw new NullPointerException();
if (sensor == null) throw new NullPointerException();
return nativeEnableSensor(nSensorEventQueue, sensor.getHandle(), rateUs,
- maxBatchReportLatencyUs, reservedFlags);
+ maxBatchReportLatencyUs);
}
private int disableSensor(Sensor sensor) {
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a0217c2..2f5e402 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -939,6 +939,129 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<Integer>("android.lens.facing", int.class);
/**
+ * <p>The orientation of the camera relative to the sensor
+ * coordinate system.</p>
+ * <p>The four coefficients that describe the quarternion
+ * rotation from the Android sensor coordinate system to a
+ * camera-aligned coordinate system where the X-axis is
+ * aligned with the long side of the image sensor, the Y-axis
+ * is aligned with the short side of the image sensor, and
+ * the Z-axis is aligned with the optical axis of the sensor.</p>
+ * <p>To convert from the quarternion coefficients <code>(x,y,z,w)</code>
+ * to the axis of rotation <code>(a_x, a_y, a_z)</code> and rotation
+ * amount <code>theta</code>, the following formulas can be used:</p>
+ * <pre><code> theta = 2 * acos(w)
+ * a_x = x / sin(theta/2)
+ * a_y = y / sin(theta/2)
+ * a_z = z / sin(theta/2)
+ * </code></pre>
+ * <p>To create a 3x3 rotation matrix that applies the rotation
+ * defined by this quarternion, the following matrix can be
+ * used:</p>
+ * <pre><code>R = [ 1 - 2y^2 - 2z^2, 2xy - 2zw, 2xz + 2yw,
+ * 2xy + 2zw, 1 - 2x^2 - 2z^2, 2yz - 2xw,
+ * 2xz - 2yw, 2yz + 2xw, 1 - 2x^2 - 2y^2 ]
+ * </code></pre>
+ * <p>This matrix can then be used to apply the rotation to a
+ * column vector point with</p>
+ * <p><code>p' = Rp</code></p>
+ * <p>where <code>p</code> is in the device sensor coordinate system, and
+ * <code>p'</code> is in the camera-oriented coordinate system.</p>
+ * <p><b>Units</b>:
+ * Quarternion coefficients</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_POSE_ROTATION =
+ new Key<float[]>("android.lens.poseRotation", float[].class);
+
+ /**
+ * <p>Position of the camera optical center.</p>
+ * <p>As measured in the device sensor coordinate system, the
+ * position of the camera device's optical center, as a
+ * three-dimensional vector <code>(x,y,z)</code>.</p>
+ * <p>To transform a world position to a camera-device centered
+ * coordinate system, the position must be translated by this
+ * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p>
+ * <p><b>Units</b>: Meters</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_POSE_TRANSLATION =
+ new Key<float[]>("android.lens.poseTranslation", float[].class);
+
+ /**
+ * <p>The parameters for this camera device's intrinsic
+ * calibration.</p>
+ * <p>The five calibration parameters that describe the
+ * transform from camera-centric 3D coordinates to sensor
+ * pixel coordinates:</p>
+ * <pre><code>[f_x, f_y, c_x, c_y, s]
+ * </code></pre>
+ * <p>Where <code>f_x</code> and <code>f_y</code> are the horizontal and vertical
+ * focal lengths, <code>[c_x, c_y]</code> is the position of the optical
+ * axis, and <code>s</code> is a skew parameter for the sensor plane not
+ * being aligned with the lens plane.</p>
+ * <p>These are typically used within a transformation matrix K:</p>
+ * <pre><code>K = [ f_x, s, c_x,
+ * 0, f_y, c_y,
+ * 0 0, 1 ]
+ * </code></pre>
+ * <p>which can then be combined with the camera pose rotation
+ * <code>R</code> and translation <code>t</code> ({@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} and
+ * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, respective) to calculate the
+ * complete transform from world coordinates to pixel
+ * coordinates:</p>
+ * <pre><code>P = [ K 0 * [ R t
+ * 0 1 ] 0 1 ]
+ * </code></pre>
+ * <p>and with <code>p_w</code> being a point in the world coordinate system
+ * and <code>p_s</code> being a point in the camera active pixel array
+ * coordinate system, and with the mapping including the
+ * homogeneous division by z:</p>
+ * <pre><code> p_h = (x_h, y_h, z_h) = P p_w
+ * p_s = p_h / z_h
+ * </code></pre>
+ * <p>so <code>[x_s, y_s]</code> is the pixel coordinates of the world
+ * point, <code>z_s = 1</code>, and <code>w_s</code> is a measurement of disparity
+ * (depth) in pixel coordinates.</p>
+ * <p><b>Units</b>:
+ * Pixels in the android.sensor.activeArraySize coordinate
+ * system.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
+ * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_INTRINSIC_CALIBRATION =
+ new Key<float[]>("android.lens.intrinsicCalibration", float[].class);
+
+ /**
+ * <p>The correction coefficients to correct for this camera device's
+ * radial lens distortion.</p>
+ * <p>Three cofficients <code>[kappa_1, kappa_2, kappa_3]</code> that
+ * can be used to correct the lens's radial geometric
+ * distortion with the mapping equations:</p>
+ * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 )
+ * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 )
+ * </code></pre>
+ * <p>where <code>[x_i, y_i]</code> are normalized coordinates with <code>(0,0)</code>
+ * at the lens optical center, and <code>[-1, 1]</code> are the edges of
+ * the active pixel array; and where <code>[x_c, y_c]</code> are the
+ * corrected normalized coordinates with radial distortion
+ * removed; and <code>r^2 = x_i^2 + y_i^2</code>.</p>
+ * <p><b>Units</b>:
+ * Coefficients for a 6th-degree even radial polynomial.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_RADIAL_DISTORTION =
+ new Key<float[]>("android.lens.radialDistortion", float[].class);
+
+ /**
* <p>List of noise reduction modes for {@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} that are supported
* by this camera device.</p>
* <p>Full-capability camera devices will always support OFF and FAST.</p>
@@ -991,7 +1114,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <ul>
* <li>Processed (but stalling): any non-RAW format with a stallDurations &gt; 0.
* Typically JPEG format (ImageFormat#JPEG).</li>
- * <li>Raw formats: ImageFormat#RAW_SENSOR, ImageFormat#RAW10 and ImageFormat#RAW_OPAQUE.</li>
+ * <li>Raw formats: ImageFormat#RAW_SENSOR, ImageFormat#RAW10, ImageFormat#RAW12,
+ * and ImageFormat#RAW_OPAQUE.</li>
* <li>Processed (but not-stalling): any non-RAW format without a stall duration.
* Typically ImageFormat#YUV_420_888, ImageFormat#NV21, ImageFormat#YV12.</li>
* </ul>
@@ -1023,6 +1147,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <ul>
* <li>ImageFormat#RAW_SENSOR</li>
* <li>ImageFormat#RAW10</li>
+ * <li>ImageFormat#RAW12</li>
* <li>Opaque <code>RAW</code></li>
* </ul>
* <p>LEGACY mode devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> LEGACY)
@@ -2352,8 +2477,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>Camera devices that support the MANUAL_POST_PROCESSING capability will always contain
* at least one of below mode combinations:</p>
* <ul>
- * <li>CONTRAST_CURVE and FAST</li>
- * <li>GAMMA_VALUE, PRESET_CURVE, and FAST</li>
+ * <li>CONTRAST_CURVE, FAST and HIGH_QUALITY</li>
+ * <li>GAMMA_VALUE, PRESET_CURVE, FAST and HIGH_QUALITY</li>
* </ul>
* <p>This includes all FULL level devices.</p>
* <p><b>Range of valid values:</b><br>
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 7f901c8..85c574a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -852,8 +852,8 @@ public abstract class CameraMetadata<TKey> {
/**
* <p>Color correction processing operates at improved
- * quality but reduced capture rate (relative to sensor raw
- * output).</p>
+ * quality but the capture rate might be reduced (relative to sensor
+ * raw output rate)</p>
* <p>Advanced white balance adjustments above and beyond
* the specified white balance pipeline may be applied.</p>
* <p>If AWB is enabled with <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != OFF</code>, then
@@ -883,8 +883,8 @@ public abstract class CameraMetadata<TKey> {
public static final int COLOR_CORRECTION_ABERRATION_MODE_FAST = 1;
/**
- * <p>Aberration correction operates at improved quality but reduced
- * capture rate (relative to sensor raw output).</p>
+ * <p>Aberration correction operates at improved quality but the capture rate might be
+ * reduced (relative to sensor raw output rate)</p>
* @see CaptureRequest#COLOR_CORRECTION_ABERRATION_MODE
*/
public static final int COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY = 2;
@@ -1797,7 +1797,7 @@ public abstract class CameraMetadata<TKey> {
public static final int EDGE_MODE_FAST = 1;
/**
- * <p>Apply high-quality edge enhancement, at a cost of reducing output frame rate.</p>
+ * <p>Apply high-quality edge enhancement, at a cost of possibly reduced output frame rate.</p>
* @see CaptureRequest#EDGE_MODE
*/
public static final int EDGE_MODE_HIGH_QUALITY = 2;
@@ -1852,7 +1852,7 @@ public abstract class CameraMetadata<TKey> {
/**
* <p>High-quality hot pixel correction is applied, at a cost
- * of reducing frame rate relative to sensor raw output.</p>
+ * of possibly reduced frame rate relative to sensor raw output.</p>
* <p>The hotpixel map may be returned in {@link CaptureResult#STATISTICS_HOT_PIXEL_MAP android.statistics.hotPixelMap}.</p>
*
* @see CaptureResult#STATISTICS_HOT_PIXEL_MAP
@@ -1894,8 +1894,8 @@ public abstract class CameraMetadata<TKey> {
public static final int NOISE_REDUCTION_MODE_FAST = 1;
/**
- * <p>High-quality noise reduction is applied, at the cost of reducing frame rate
- * relative to sensor output.</p>
+ * <p>High-quality noise reduction is applied, at the cost of possibly reduced frame
+ * rate relative to sensor output.</p>
* @see CaptureRequest#NOISE_REDUCTION_MODE
*/
public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2;
@@ -2032,7 +2032,7 @@ public abstract class CameraMetadata<TKey> {
/**
* <p>Apply high-quality lens shading correction, at the
- * cost of reduced frame rate.</p>
+ * cost of possibly reduced frame rate.</p>
* @see CaptureRequest#SHADING_MODE
*/
public static final int SHADING_MODE_HIGH_QUALITY = 2;
@@ -2105,7 +2105,7 @@ public abstract class CameraMetadata<TKey> {
/**
* <p>High-quality gamma mapping and color enhancement will be applied, at
- * the cost of reduced frame rate compared to raw sensor output.</p>
+ * the cost of possibly reduced frame rate compared to raw sensor output.</p>
* @see CaptureRequest#TONEMAP_MODE
*/
public static final int TONEMAP_MODE_HIGH_QUALITY = 2;
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 7569ea5..b8fb8e7 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1541,7 +1541,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* to the camera, that the JPEG picture needs to be rotated by, to be viewed
* upright.</p>
* <p>Camera devices may either encode this value into the JPEG EXIF header, or
- * rotate the image data to match this orientation.</p>
+ * rotate the image data to match this orientation. When the image data is rotated,
+ * the thumbnail data will also be rotated.</p>
* <p>Note that this orientation is relative to the orientation of the camera sensor, given
* by {@link CameraCharacteristics#SENSOR_ORIENTATION android.sensor.orientation}.</p>
* <p>To translate from the device orientation given by the Android sensor APIs, the following
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index b84dc2e..d8f92e5 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2230,7 +2230,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* to the camera, that the JPEG picture needs to be rotated by, to be viewed
* upright.</p>
* <p>Camera devices may either encode this value into the JPEG EXIF header, or
- * rotate the image data to match this orientation.</p>
+ * rotate the image data to match this orientation. When the image data is rotated,
+ * the thumbnail data will also be rotated.</p>
* <p>Note that this orientation is relative to the orientation of the camera sensor, given
* by {@link CameraCharacteristics#SENSOR_ORIENTATION android.sensor.orientation}.</p>
* <p>To translate from the device orientation given by the Android sensor APIs, the following
@@ -2531,6 +2532,129 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
new Key<Integer>("android.lens.state", int.class);
/**
+ * <p>The orientation of the camera relative to the sensor
+ * coordinate system.</p>
+ * <p>The four coefficients that describe the quarternion
+ * rotation from the Android sensor coordinate system to a
+ * camera-aligned coordinate system where the X-axis is
+ * aligned with the long side of the image sensor, the Y-axis
+ * is aligned with the short side of the image sensor, and
+ * the Z-axis is aligned with the optical axis of the sensor.</p>
+ * <p>To convert from the quarternion coefficients <code>(x,y,z,w)</code>
+ * to the axis of rotation <code>(a_x, a_y, a_z)</code> and rotation
+ * amount <code>theta</code>, the following formulas can be used:</p>
+ * <pre><code> theta = 2 * acos(w)
+ * a_x = x / sin(theta/2)
+ * a_y = y / sin(theta/2)
+ * a_z = z / sin(theta/2)
+ * </code></pre>
+ * <p>To create a 3x3 rotation matrix that applies the rotation
+ * defined by this quarternion, the following matrix can be
+ * used:</p>
+ * <pre><code>R = [ 1 - 2y^2 - 2z^2, 2xy - 2zw, 2xz + 2yw,
+ * 2xy + 2zw, 1 - 2x^2 - 2z^2, 2yz - 2xw,
+ * 2xz - 2yw, 2yz + 2xw, 1 - 2x^2 - 2y^2 ]
+ * </code></pre>
+ * <p>This matrix can then be used to apply the rotation to a
+ * column vector point with</p>
+ * <p><code>p' = Rp</code></p>
+ * <p>where <code>p</code> is in the device sensor coordinate system, and
+ * <code>p'</code> is in the camera-oriented coordinate system.</p>
+ * <p><b>Units</b>:
+ * Quarternion coefficients</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_POSE_ROTATION =
+ new Key<float[]>("android.lens.poseRotation", float[].class);
+
+ /**
+ * <p>Position of the camera optical center.</p>
+ * <p>As measured in the device sensor coordinate system, the
+ * position of the camera device's optical center, as a
+ * three-dimensional vector <code>(x,y,z)</code>.</p>
+ * <p>To transform a world position to a camera-device centered
+ * coordinate system, the position must be translated by this
+ * vector and then rotated by {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p>
+ * <p><b>Units</b>: Meters</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_POSE_TRANSLATION =
+ new Key<float[]>("android.lens.poseTranslation", float[].class);
+
+ /**
+ * <p>The parameters for this camera device's intrinsic
+ * calibration.</p>
+ * <p>The five calibration parameters that describe the
+ * transform from camera-centric 3D coordinates to sensor
+ * pixel coordinates:</p>
+ * <pre><code>[f_x, f_y, c_x, c_y, s]
+ * </code></pre>
+ * <p>Where <code>f_x</code> and <code>f_y</code> are the horizontal and vertical
+ * focal lengths, <code>[c_x, c_y]</code> is the position of the optical
+ * axis, and <code>s</code> is a skew parameter for the sensor plane not
+ * being aligned with the lens plane.</p>
+ * <p>These are typically used within a transformation matrix K:</p>
+ * <pre><code>K = [ f_x, s, c_x,
+ * 0, f_y, c_y,
+ * 0 0, 1 ]
+ * </code></pre>
+ * <p>which can then be combined with the camera pose rotation
+ * <code>R</code> and translation <code>t</code> ({@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} and
+ * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, respective) to calculate the
+ * complete transform from world coordinates to pixel
+ * coordinates:</p>
+ * <pre><code>P = [ K 0 * [ R t
+ * 0 1 ] 0 1 ]
+ * </code></pre>
+ * <p>and with <code>p_w</code> being a point in the world coordinate system
+ * and <code>p_s</code> being a point in the camera active pixel array
+ * coordinate system, and with the mapping including the
+ * homogeneous division by z:</p>
+ * <pre><code> p_h = (x_h, y_h, z_h) = P p_w
+ * p_s = p_h / z_h
+ * </code></pre>
+ * <p>so <code>[x_s, y_s]</code> is the pixel coordinates of the world
+ * point, <code>z_s = 1</code>, and <code>w_s</code> is a measurement of disparity
+ * (depth) in pixel coordinates.</p>
+ * <p><b>Units</b>:
+ * Pixels in the android.sensor.activeArraySize coordinate
+ * system.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
+ * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_INTRINSIC_CALIBRATION =
+ new Key<float[]>("android.lens.intrinsicCalibration", float[].class);
+
+ /**
+ * <p>The correction coefficients to correct for this camera device's
+ * radial lens distortion.</p>
+ * <p>Three cofficients <code>[kappa_1, kappa_2, kappa_3]</code> that
+ * can be used to correct the lens's radial geometric
+ * distortion with the mapping equations:</p>
+ * <pre><code> x_c = x_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 )
+ * y_c = y_i * ( 1 + kappa_1 * r^2 + kappa_2 * r^4 + kappa_3 * r^6 )
+ * </code></pre>
+ * <p>where <code>[x_i, y_i]</code> are normalized coordinates with <code>(0,0)</code>
+ * at the lens optical center, and <code>[-1, 1]</code> are the edges of
+ * the active pixel array; and where <code>[x_c, y_c]</code> are the
+ * corrected normalized coordinates with radial distortion
+ * removed; and <code>r^2 = x_i^2 + y_i^2</code>.</p>
+ * <p><b>Units</b>:
+ * Coefficients for a 6th-degree even radial polynomial.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ */
+ @PublicKey
+ public static final Key<float[]> LENS_RADIAL_DISTORTION =
+ new Key<float[]>("android.lens.radialDistortion", float[].class);
+
+ /**
* <p>Mode of operation for the noise reduction algorithm.</p>
* <p>The noise reduction algorithm attempts to improve image quality by removing
* excessive noise added by the capture process, especially in dark conditions.</p>
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index f47ce79..e926a98 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -830,11 +830,19 @@ public class CameraMetadataNative implements Parcelable {
CameraCharacteristics.SCALER_AVAILABLE_MIN_FRAME_DURATIONS);
StreamConfigurationDuration[] stallDurations = getBase(
CameraCharacteristics.SCALER_AVAILABLE_STALL_DURATIONS);
+ StreamConfiguration[] depthConfigurations = getBase(
+ CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS);
+ StreamConfigurationDuration[] depthMinFrameDurations = getBase(
+ CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_MIN_FRAME_DURATIONS);
+ StreamConfigurationDuration[] depthStallDurations = getBase(
+ CameraCharacteristics.DEPTH_AVAILABLE_DEPTH_STALL_DURATIONS);
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations = getBase(
CameraCharacteristics.CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS);
return new StreamConfigurationMap(
- configurations, minFrameDurations, stallDurations, highSpeedVideoConfigurations);
+ configurations, minFrameDurations, stallDurations,
+ depthConfigurations, depthMinFrameDurations, depthStallDurations,
+ highSpeedVideoConfigurations);
}
private <T> Integer getMaxRegions(Key<T> key) {
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 0a4ed39..7aa9787 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -18,6 +18,7 @@
package android.hardware.camera2.params;
import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.utils.HashCodeHelpers;
import android.util.Log;
import android.view.Surface;
import android.os.Parcel;
@@ -159,6 +160,35 @@ public final class OutputConfiguration implements Parcelable {
mSurface.writeToParcel(dest, flags);
}
+ /**
+ * Check if this {@link OutputConfiguration} is equal to another {@link OutputConfiguration}.
+ *
+ * <p>Two output configurations are only equal if and only if the underlying surface and
+ * all other configuration parameters are equal. </p>
+ *
+ * @return {@code true} if the objects were equal, {@code false} otherwise
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ } else if (this == obj) {
+ return true;
+ } else if (obj instanceof OutputConfiguration) {
+ final OutputConfiguration other = (OutputConfiguration) obj;
+ return (mSurface == other.mSurface && mRotation == other.mRotation);
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return HashCodeHelpers.hashCode(mSurface.hashCode(), mRotation);
+ }
+
private static final String TAG = "OutputConfiguration";
private final Surface mSurface;
private final int mRotation;
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index f5304f8..b418971 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -89,11 +89,28 @@ public final class StreamConfigurationMap {
StreamConfiguration[] configurations,
StreamConfigurationDuration[] minFrameDurations,
StreamConfigurationDuration[] stallDurations,
+ StreamConfiguration[] depthConfigurations,
+ StreamConfigurationDuration[] depthMinFrameDurations,
+ StreamConfigurationDuration[] depthStallDurations,
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations) {
mConfigurations = checkArrayElementsNotNull(configurations, "configurations");
mMinFrameDurations = checkArrayElementsNotNull(minFrameDurations, "minFrameDurations");
mStallDurations = checkArrayElementsNotNull(stallDurations, "stallDurations");
+
+ if (depthConfigurations == null) {
+ mDepthConfigurations = new StreamConfiguration[0];
+ mDepthMinFrameDurations = new StreamConfigurationDuration[0];
+ mDepthStallDurations = new StreamConfigurationDuration[0];
+ } else {
+ mDepthConfigurations = checkArrayElementsNotNull(depthConfigurations,
+ "depthConfigurations");
+ mDepthMinFrameDurations = checkArrayElementsNotNull(depthMinFrameDurations,
+ "depthMinFrameDurations");
+ mDepthStallDurations = checkArrayElementsNotNull(depthStallDurations,
+ "depthStallDurations");
+ }
+
if (highSpeedVideoConfigurations == null) {
mHighSpeedVideoConfigurations = new HighSpeedVideoConfiguration[0];
} else {
@@ -110,9 +127,24 @@ public final class StreamConfigurationMap {
if (count == null) {
count = 0;
}
- count = count + 1;
- map.put(config.getFormat(), count);
+ map.put(config.getFormat(), count + 1);
+ }
+
+ // For each depth format, track how many sizes there are available to configure
+ for (StreamConfiguration config : mDepthConfigurations) {
+ if (!config.isOutput()) {
+ // Ignoring input depth configs
+ continue;
+ }
+
+ Integer count = mDepthOutputFormats.get(config.getFormat());
+
+ if (count == null) {
+ count = 0;
+ }
+
+ mDepthOutputFormats.put(config.getFormat(), count + 1);
}
if (!mOutputFormats.containsKey(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED)) {
@@ -214,8 +246,13 @@ public final class StreamConfigurationMap {
public boolean isOutputSupportedFor(int format) {
checkArgumentFormat(format);
- format = imageFormatToInternal(format);
- return getFormatsMap(/*output*/true).containsKey(format);
+ int internalFormat = imageFormatToInternal(format);
+ int dataspace = imageFormatToDataspace(format);
+ if (dataspace == HAL_DATASPACE_DEPTH) {
+ return mDepthOutputFormats.containsKey(internalFormat);
+ } else {
+ return getFormatsMap(/*output*/true).containsKey(internalFormat);
+ }
}
/**
@@ -386,7 +423,8 @@ public final class StreamConfigurationMap {
return null;
}
- return getInternalFormatSizes(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED, /*output*/true);
+ return getInternalFormatSizes(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED,
+ HAL_DATASPACE_UNKNOWN,/*output*/true);
}
/**
@@ -599,7 +637,10 @@ public final class StreamConfigurationMap {
checkNotNull(size, "size must not be null");
checkArgumentFormatSupported(format, /*output*/true);
- return getInternalFormatDuration(imageFormatToInternal(format), size, DURATION_MIN_FRAME);
+ return getInternalFormatDuration(imageFormatToInternal(format),
+ imageFormatToDataspace(format),
+ size,
+ DURATION_MIN_FRAME);
}
/**
@@ -652,6 +693,7 @@ public final class StreamConfigurationMap {
}
return getInternalFormatDuration(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED,
+ HAL_DATASPACE_UNKNOWN,
size, DURATION_MIN_FRAME);
}
@@ -741,7 +783,9 @@ public final class StreamConfigurationMap {
checkArgumentFormatSupported(format, /*output*/true);
return getInternalFormatDuration(imageFormatToInternal(format),
- size, DURATION_STALL);
+ imageFormatToDataspace(format),
+ size,
+ DURATION_STALL);
}
/**
@@ -778,7 +822,7 @@ public final class StreamConfigurationMap {
}
return getInternalFormatDuration(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED,
- size, DURATION_STALL);
+ HAL_DATASPACE_UNKNOWN, size, DURATION_STALL);
}
/**
@@ -857,6 +901,7 @@ public final class StreamConfigurationMap {
case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
case HAL_PIXEL_FORMAT_BLOB:
case HAL_PIXEL_FORMAT_RAW_OPAQUE:
+ case HAL_PIXEL_FORMAT_Y16:
return format;
case ImageFormat.JPEG:
throw new IllegalArgumentException(
@@ -896,8 +941,8 @@ public final class StreamConfigurationMap {
}
/**
- * Convert a public-visible {@code ImageFormat} into an internal format
- * compatible with {@code graphics.h}.
+ * Convert an internal format compatible with {@code graphics.h} into public-visible
+ * {@code ImageFormat}. This assumes the dataspace of the format is not HAL_DATASPACE_DEPTH.
*
* <p>In particular these formats are converted:
* <ul>
@@ -911,7 +956,8 @@ public final class StreamConfigurationMap {
*
* <p>All other formats are returned as-is, no further invalid check is performed.</p>
*
- * <p>This function is the dual of {@link #imageFormatToInternal}.</p>
+ * <p>This function is the dual of {@link #imageFormatToInternal} for dataspaces other than
+ * HAL_DATASPACE_DEPTH.</p>
*
* @param format image format from {@link ImageFormat} or {@link PixelFormat}
* @return the converted image formats
@@ -940,6 +986,55 @@ public final class StreamConfigurationMap {
}
/**
+ * Convert an internal format compatible with {@code graphics.h} into public-visible
+ * {@code ImageFormat}. This assumes the dataspace of the format is HAL_DATASPACE_DEPTH.
+ *
+ * <p>In particular these formats are converted:
+ * <ul>
+ * <li>HAL_PIXEL_FORMAT_BLOB => ImageFormat.DEPTH_POINT_CLOUD
+ * <li>HAL_PIXEL_FORMAT_Y16 => ImageFormat.DEPTH16
+ * </ul>
+ * </p>
+ *
+ * <p>Passing in an implementation-defined format which has no public equivalent will fail;
+ * as will passing in a public format which has a different internal format equivalent.
+ * See {@link #checkArgumentFormat} for more details about a legal public format.</p>
+ *
+ * <p>All other formats are returned as-is, no further invalid check is performed.</p>
+ *
+ * <p>This function is the dual of {@link #imageFormatToInternal} for formats associated with
+ * HAL_DATASPACE_DEPTH.</p>
+ *
+ * @param format image format from {@link ImageFormat} or {@link PixelFormat}
+ * @return the converted image formats
+ *
+ * @throws IllegalArgumentException
+ * if {@code format} is {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED} or
+ * {@link ImageFormat#JPEG}
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ * @see #checkArgumentFormat
+ */
+ static int depthFormatToPublic(int format) {
+ switch (format) {
+ case HAL_PIXEL_FORMAT_BLOB:
+ return ImageFormat.DEPTH_POINT_CLOUD;
+ case HAL_PIXEL_FORMAT_Y16:
+ return ImageFormat.DEPTH16;
+ case ImageFormat.JPEG:
+ throw new IllegalArgumentException(
+ "ImageFormat.JPEG is an unknown internal format");
+ case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+ throw new IllegalArgumentException(
+ "IMPLEMENTATION_DEFINED must not leak to public API");
+ default:
+ throw new IllegalArgumentException(
+ "Unknown DATASPACE_DEPTH format " + format);
+ }
+ }
+
+ /**
* Convert image formats from internal to public formats (in-place).
*
* @param formats an array of image formats
@@ -966,6 +1061,8 @@ public final class StreamConfigurationMap {
* <p>In particular these formats are converted:
* <ul>
* <li>ImageFormat.JPEG => HAL_PIXEL_FORMAT_BLOB
+ * <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_PIXEL_FORMAT_BLOB
+ * <li>ImageFormat.DEPTH16 => HAL_PIXEL_FORMAT_Y16
* </ul>
* </p>
*
@@ -989,7 +1086,10 @@ public final class StreamConfigurationMap {
static int imageFormatToInternal(int format) {
switch (format) {
case ImageFormat.JPEG:
+ case ImageFormat.DEPTH_POINT_CLOUD:
return HAL_PIXEL_FORMAT_BLOB;
+ case ImageFormat.DEPTH16:
+ return HAL_PIXEL_FORMAT_Y16;
case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
throw new IllegalArgumentException(
"IMPLEMENTATION_DEFINED is not allowed via public API");
@@ -999,6 +1099,48 @@ public final class StreamConfigurationMap {
}
/**
+ * Convert a public format compatible with {@code ImageFormat} to an internal dataspace
+ * from {@code graphics.h}.
+ *
+ * <p>In particular these formats are converted:
+ * <ul>
+ * <li>ImageFormat.JPEG => HAL_DATASPACE_JFIF
+ * <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_DATASPACE_DEPTH
+ * <li>ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH
+ * <li>others => HAL_DATASPACE_UNKNOWN
+ * </ul>
+ * </p>
+ *
+ * <p>Passing in an implementation-defined format here will fail (it's not a public format);
+ * as will passing in an internal format which has a different public format equivalent.
+ * See {@link #checkArgumentFormat} for more details about a legal public format.</p>
+ *
+ * <p>All other formats are returned as-is, no invalid check is performed.</p>
+ *
+ * <p>This function is the dual of {@link #imageFormatToPublic}.</p>
+ *
+ * @param format public image format from {@link ImageFormat} or {@link PixelFormat}
+ * @return the converted image formats
+ *
+ * @see ImageFormat
+ * @see PixelFormat
+ *
+ * @throws IllegalArgumentException
+ * if {@code format} was {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED}
+ */
+ static int imageFormatToDataspace(int format) {
+ switch (format) {
+ case ImageFormat.JPEG:
+ return HAL_DATASPACE_JFIF;
+ case ImageFormat.DEPTH_POINT_CLOUD:
+ case ImageFormat.DEPTH16:
+ return HAL_DATASPACE_DEPTH;
+ default:
+ return HAL_DATASPACE_UNKNOWN;
+ }
+ }
+
+ /**
* Convert image formats from public to internal formats (in-place).
*
* @param formats an array of image formats
@@ -1027,13 +1169,16 @@ public final class StreamConfigurationMap {
return null;
}
- format = imageFormatToInternal(format);
+ int internalFormat = imageFormatToInternal(format);
+ int dataspace = imageFormatToDataspace(format);
- return getInternalFormatSizes(format, output);
+ return getInternalFormatSizes(internalFormat, dataspace, output);
}
- private Size[] getInternalFormatSizes(int format, boolean output) {
- HashMap<Integer, Integer> formatsMap = getFormatsMap(output);
+ private Size[] getInternalFormatSizes(int format, int dataspace, boolean output) {
+
+ HashMap<Integer, Integer> formatsMap =
+ (dataspace == HAL_DATASPACE_DEPTH) ? mDepthOutputFormats : getFormatsMap(output);
Integer sizesCount = formatsMap.get(format);
if (sizesCount == null) {
@@ -1044,7 +1189,11 @@ public final class StreamConfigurationMap {
Size[] sizes = new Size[len];
int sizeIndex = 0;
- for (StreamConfiguration config : mConfigurations) {
+ StreamConfiguration[] configurations =
+ (dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations : mConfigurations;
+
+
+ for (StreamConfiguration config : configurations) {
if (config.getFormat() == format && config.isOutput() == output) {
sizes[sizeIndex++] = config.getSize();
}
@@ -1067,15 +1216,19 @@ public final class StreamConfigurationMap {
for (int format : getFormatsMap(output).keySet()) {
if (format != HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED &&
format != HAL_PIXEL_FORMAT_RAW_OPAQUE) {
- formats[i++] = format;
+ formats[i++] = imageFormatToPublic(format);
+ }
+ }
+ if (output) {
+ for (int format : mDepthOutputFormats.keySet()) {
+ formats[i++] = depthFormatToPublic(format);
}
}
-
if (formats.length != i) {
throw new AssertionError("Too few formats " + i + ", expected " + formats.length);
}
- return imageFormatToPublic(formats);
+ return formats;
}
/** Get the format -> size count map for either output or input formats */
@@ -1083,14 +1236,14 @@ public final class StreamConfigurationMap {
return output ? mOutputFormats : mInputFormats;
}
- private long getInternalFormatDuration(int format, Size size, int duration) {
+ private long getInternalFormatDuration(int format, int dataspace, Size size, int duration) {
// assume format is already checked, since its internal
- if (!arrayContains(getInternalFormatSizes(format, /*output*/true), size)) {
+ if (!arrayContains(getInternalFormatSizes(format, dataspace, /*output*/true), size)) {
throw new IllegalArgumentException("size was not supported");
}
- StreamConfigurationDuration[] durations = getDurations(duration);
+ StreamConfigurationDuration[] durations = getDurations(duration, dataspace);
for (StreamConfigurationDuration configurationDuration : durations) {
if (configurationDuration.getFormat() == format &&
@@ -1109,12 +1262,14 @@ public final class StreamConfigurationMap {
* @see #DURATION_MIN_FRAME
* @see #DURATION_STALL
* */
- private StreamConfigurationDuration[] getDurations(int duration) {
+ private StreamConfigurationDuration[] getDurations(int duration, int dataspace) {
switch (duration) {
case DURATION_MIN_FRAME:
- return mMinFrameDurations;
+ return (dataspace == HAL_DATASPACE_DEPTH) ?
+ mDepthMinFrameDurations : mMinFrameDurations;
case DURATION_STALL:
- return mStallDurations;
+ return (dataspace == HAL_DATASPACE_DEPTH) ?
+ mDepthStallDurations : mStallDurations;
default:
throw new IllegalArgumentException("duration was invalid");
}
@@ -1131,6 +1286,9 @@ public final class StreamConfigurationMap {
if (formatsMap.containsKey(HAL_PIXEL_FORMAT_RAW_OPAQUE)) {
size -= 1;
}
+ if (output) {
+ size += mDepthOutputFormats.size();
+ }
return size;
}
@@ -1153,10 +1311,14 @@ public final class StreamConfigurationMap {
private static final int HAL_PIXEL_FORMAT_BLOB = 0x21;
private static final int HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED = 0x22;
private static final int HAL_PIXEL_FORMAT_RAW_OPAQUE = 0x24;
+ private static final int HAL_PIXEL_FORMAT_Y16 = 0x20363159;
+
+ private static final int HAL_DATASPACE_UNKNOWN = 0x0;
+ private static final int HAL_DATASPACE_JFIF = 0x101;
+ private static final int HAL_DATASPACE_DEPTH = 0x1000;
/**
- * @see #getDurations(int)
- * @see #getDurationDefault(int)
+ * @see #getDurations(int, int)
*/
private static final int DURATION_MIN_FRAME = 0;
private static final int DURATION_STALL = 1;
@@ -1164,6 +1326,11 @@ public final class StreamConfigurationMap {
private final StreamConfiguration[] mConfigurations;
private final StreamConfigurationDuration[] mMinFrameDurations;
private final StreamConfigurationDuration[] mStallDurations;
+
+ private final StreamConfiguration[] mDepthConfigurations;
+ private final StreamConfigurationDuration[] mDepthMinFrameDurations;
+ private final StreamConfigurationDuration[] mDepthStallDurations;
+
private final HighSpeedVideoConfiguration[] mHighSpeedVideoConfigurations;
/** ImageFormat -> num output sizes mapping */
@@ -1172,6 +1339,9 @@ public final class StreamConfigurationMap {
/** ImageFormat -> num input sizes mapping */
private final HashMap</*ImageFormat*/Integer, /*Count*/Integer> mInputFormats =
new HashMap<Integer, Integer>();
+ /** ImageFormat -> num depth output sizes mapping */
+ private final HashMap</*ImageFormat*/Integer, /*Count*/Integer> mDepthOutputFormats =
+ new HashMap<Integer, Integer>();
/** High speed video Size -> FPS range count mapping*/
private final HashMap</*HighSpeedVideoSize*/Size, /*Count*/Integer> mHighSpeedVideoSizeMap =
new HashMap<Size, Integer>();
diff --git a/core/java/android/service/fingerprint/Fingerprint.aidl b/core/java/android/hardware/fingerprint/Fingerprint.aidl
index c9fd989..4743354 100644
--- a/core/java/android/service/fingerprint/Fingerprint.aidl
+++ b/core/java/android/hardware/fingerprint/Fingerprint.aidl
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.service.fingerprint;
+package android.hardware.fingerprint;
// @hide
parcelable Fingerprint;
diff --git a/core/java/android/service/fingerprint/Fingerprint.java b/core/java/android/hardware/fingerprint/Fingerprint.java
index 37552eb..c307634 100644
--- a/core/java/android/service/fingerprint/Fingerprint.java
+++ b/core/java/android/hardware/fingerprint/Fingerprint.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.service.fingerprint;
+package android.hardware.fingerprint;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/service/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index bb90e40..e3572a2 100644
--- a/core/java/android/service/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.service.fingerprint;
+package android.hardware.fingerprint;
import android.app.ActivityManagerNative;
import android.content.ContentResolver;
@@ -28,7 +28,7 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
-import android.service.fingerprint.FingerprintManager.EnrollmentCallback;
+import android.hardware.fingerprint.FingerprintManager.EnrollmentCallback;
import android.util.Log;
import android.util.Slog;
diff --git a/core/java/android/service/fingerprint/FingerprintUtils.java b/core/java/android/hardware/fingerprint/FingerprintUtils.java
index 62acbb9..ae3d4a4 100644
--- a/core/java/android/service/fingerprint/FingerprintUtils.java
+++ b/core/java/android/hardware/fingerprint/FingerprintUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.service.fingerprint;
+package android.hardware.fingerprint;
import android.content.ContentResolver;
import android.provider.Settings;
diff --git a/core/java/android/service/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index e5d3ad4..c5a45e2 100644
--- a/core/java/android/service/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.service.fingerprint;
+package android.hardware.fingerprint;
import android.os.Bundle;
-import android.service.fingerprint.IFingerprintServiceReceiver;
-import android.service.fingerprint.Fingerprint;
+import android.hardware.fingerprint.IFingerprintServiceReceiver;
+import android.hardware.fingerprint.Fingerprint;
import java.util.List;
/**
diff --git a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
index f025064..e82395f 100644
--- a/core/java/android/service/fingerprint/IFingerprintServiceReceiver.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceReceiver.aidl
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.service.fingerprint;
+package android.hardware.fingerprint;
import android.os.Bundle;
import android.os.UserHandle;
diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java
index a94c1da..a336e5c 100644
--- a/core/java/android/hardware/hdmi/HdmiTvClient.java
+++ b/core/java/android/hardware/hdmi/HdmiTvClient.java
@@ -168,7 +168,22 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Sets system audio volume
+ * Sets system audio mode.
+ *
+ * @param enabled set to {@code true} to enable the mode; otherwise {@code false}
+ * @param callback callback to get the result with
+ * @throws {@link IllegalArgumentException} if the {@code callback} is null
+ */
+ public void setSystemAudioMode(boolean enabled, SelectCallback callback) {
+ try {
+ mService.setSystemAudioMode(enabled, getCallbackWrapper(callback));
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to set system audio mode:", e);
+ }
+ }
+
+ /**
+ * Sets system audio volume.
*
* @param oldIndex current volume index
* @param newIndex volume index to be set
@@ -183,7 +198,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Sets system audio mute status
+ * Sets system audio mute status.
*
* @param mute {@code true} if muted; otherwise, {@code false}
*/
@@ -196,7 +211,7 @@ public final class HdmiTvClient extends HdmiClient {
}
/**
- * Sets record listener
+ * Sets record listener.
*
* @param listener
*/
diff --git a/core/java/android/inputmethodservice/ExtractEditLayout.java b/core/java/android/inputmethodservice/ExtractEditLayout.java
index 5696839..e902443 100644
--- a/core/java/android/inputmethodservice/ExtractEditLayout.java
+++ b/core/java/android/inputmethodservice/ExtractEditLayout.java
@@ -163,6 +163,8 @@ public class ExtractEditLayout extends LinearLayout {
mCallback.onDestroyActionMode(this);
mCallback = null;
+ mMenu.close();
+
mExtractActionButton.setVisibility(VISIBLE);
mEditButton.setVisibility(INVISIBLE);
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a0e2bf8..3abccbc 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -40,6 +40,7 @@ import android.telephony.SubscriptionManager;
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.net.VpnConfig;
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.Protocol;
@@ -2447,6 +2448,38 @@ public class ConnectivityManager {
}
/**
+ * Resets all connectivity manager settings back to factory defaults.
+ * @hide
+ */
+ public void factoryReset() {
+ // Turn airplane mode off
+ setAirplaneMode(false);
+
+ // Untether
+ for (String tether : getTetheredIfaces()) {
+ untether(tether);
+ }
+
+ // Turn VPN off
+ try {
+ VpnConfig vpnConfig = mService.getVpnConfig();
+ if (vpnConfig != null) {
+ if (vpnConfig.legacy) {
+ mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
+ } else {
+ // Prevent this app from initiating VPN connections in the future without
+ // user intervention.
+ mService.setVpnPackageAuthorization(false);
+
+ mService.prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN);
+ }
+ }
+ } catch (RemoteException e) {
+ // Well, we tried
+ }
+ }
+
+ /**
* Binds the current process to {@code network}. All Sockets created in the future
* (and not explicitly bound via a bound SocketFactory from
* {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index a8e7757..a7ffee9 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -180,6 +180,33 @@ public class NetworkPolicyManager {
}
/**
+ * Resets network policy settings back to factory defaults.
+ *
+ * @hide
+ */
+ public void factoryReset(String subscriber) {
+ // Turn mobile data limit off
+ NetworkPolicy[] policies = getNetworkPolicies();
+ NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriber);
+ for (NetworkPolicy policy : policies) {
+ if (policy.template.equals(template)) {
+ policy.limitBytes = NetworkPolicy.LIMIT_DISABLED;
+ policy.inferred = false;
+ policy.clearSnooze();
+ }
+ }
+ setNetworkPolicies(policies);
+
+ // Turn restrict background data off
+ setRestrictBackground(false);
+
+ // Remove app's "restrict background data" flag
+ for (int uid : getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND)) {
+ setUidPolicy(uid, NetworkPolicyManager.POLICY_NONE);
+ }
+ }
+
+ /**
* Compute the last cycle boundary for the given {@link NetworkPolicy}. For
* example, if cycle day is 20th, and today is June 15th, it will return May
* 20th. When cycle day doesn't exist in current month, it snaps to the 1st
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 0766253..77d7e0c 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -50,12 +50,19 @@ public class NetworkStats implements Parcelable {
public static final int UID_ALL = -1;
/** {@link #tag} value matching any tag. */
public static final int TAG_ALL = -1;
- /** {@link #set} value when all sets combined. */
+ /** {@link #set} value when all sets combined, not including debug sets. */
public static final int SET_ALL = -1;
/** {@link #set} value where background data is accounted. */
public static final int SET_DEFAULT = 0;
/** {@link #set} value where foreground data is accounted. */
public static final int SET_FOREGROUND = 1;
+ /** All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. */
+ public static final int SET_DEBUG_START = 1000;
+ /** Debug {@link #set} value when the VPN stats are moved in. */
+ public static final int SET_DBG_VPN_IN = 1001;
+ /** Debug {@link #set} value when the VPN stats are moved out of a vpn UID. */
+ public static final int SET_DBG_VPN_OUT = 1002;
+
/** {@link #tag} value for total data across all tags. */
public static final int TAG_NONE = 0;
@@ -729,6 +736,10 @@ public class NetworkStats implements Parcelable {
return "DEFAULT";
case SET_FOREGROUND:
return "FOREGROUND";
+ case SET_DBG_VPN_IN:
+ return "DBG_VPN_IN";
+ case SET_DBG_VPN_OUT:
+ return "DBG_VPN_OUT";
default:
return "UNKNOWN";
}
@@ -745,12 +756,27 @@ public class NetworkStats implements Parcelable {
return "def";
case SET_FOREGROUND:
return "fg";
+ case SET_DBG_VPN_IN:
+ return "vpnin";
+ case SET_DBG_VPN_OUT:
+ return "vpnout";
default:
return "unk";
}
}
/**
+ * @return true if the querySet matches the dataSet.
+ */
+ public static boolean setMatches(int querySet, int dataSet) {
+ if (querySet == dataSet) {
+ return true;
+ }
+ // SET_ALL matches all non-debugging sets.
+ return querySet == SET_ALL && dataSet < SET_DEBUG_START;
+ }
+
+ /**
* Return text description of {@link #tag} value.
*/
public static String tagToString(int tag) {
@@ -843,6 +869,9 @@ public class NetworkStats implements Parcelable {
if (recycle.uid == UID_ALL) {
throw new IllegalStateException(
"Cannot adjust VPN accounting on an iface aggregated NetworkStats.");
+ } if (recycle.set == SET_DBG_VPN_IN || recycle.set == SET_DBG_VPN_OUT) {
+ throw new IllegalStateException(
+ "Cannot adjust VPN accounting on a NetworkStats containing SET_DBG_VPN_*");
}
if (recycle.uid == tunUid && recycle.tag == TAG_NONE
@@ -906,6 +935,9 @@ public class NetworkStats implements Parcelable {
combineValues(tmpEntry);
if (tag[i] == TAG_NONE) {
moved.add(tmpEntry);
+ // Add debug info
+ tmpEntry.set = SET_DBG_VPN_IN;
+ combineValues(tmpEntry);
}
}
}
@@ -913,6 +945,13 @@ public class NetworkStats implements Parcelable {
}
private void deductTrafficFromVpnApp(int tunUid, String underlyingIface, Entry moved) {
+ // Add debug info
+ moved.uid = tunUid;
+ moved.set = SET_DBG_VPN_OUT;
+ moved.tag = TAG_NONE;
+ moved.iface = underlyingIface;
+ combineValues(moved);
+
// Caveat: if the vpn software uses tag, the total tagged traffic may be greater than
// the TAG_NONE traffic.
int idxVpnBackground = findIndex(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE);
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 3f18519..ff3de2b 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -51,6 +51,8 @@ public class TrafficStats {
public static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
/** @hide */
public static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+ /** @hide */
+ public static final long TB_IN_BYTES = GB_IN_BYTES * 1024;
/**
* Special UID value used when collecting {@link NetworkStatsHistory} for
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index bf3d9aa..f305b2a 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -366,7 +366,6 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
public String toSafeString() {
String scheme = getScheme();
String ssp = getSchemeSpecificPart();
- String authority = null;
if (scheme != null) {
if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip")
|| scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto")
@@ -385,9 +384,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
}
}
return builder.toString();
- } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) {
- ssp = null;
- authority = "//" + getAuthority() + "/...";
+ } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")
+ || scheme.equalsIgnoreCase("ftp")) {
+ ssp = "//" + ((getHost() != null) ? getHost() : "")
+ + ((getPort() != -1) ? (":" + getPort()) : "")
+ + "/...";
}
}
// Not a sensitive scheme, but let's still be conservative about
@@ -401,9 +402,6 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
if (ssp != null) {
builder.append(ssp);
}
- if (authority != null) {
- builder.append(authority);
- }
return builder.toString();
}
diff --git a/core/java/android/net/WifiKey.java b/core/java/android/net/WifiKey.java
index 71df2f9..99de99e 100644
--- a/core/java/android/net/WifiKey.java
+++ b/core/java/android/net/WifiKey.java
@@ -33,7 +33,8 @@ import java.util.regex.Pattern;
public class WifiKey implements Parcelable {
// Patterns used for validation.
- private static final Pattern SSID_PATTERN = Pattern.compile("(\".*\")|(0x[\\p{XDigit}]+)");
+ private static final Pattern SSID_PATTERN = Pattern.compile("(\".*\")|(0x[\\p{XDigit}]+)",
+ Pattern.DOTALL);
private static final Pattern BSSID_PATTERN =
Pattern.compile("([\\p{XDigit}]{2}:){5}[\\p{XDigit}]{2}");
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 1a06e0a..3051926 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -269,6 +269,15 @@ public abstract class BatteryStats implements Parcelable {
public abstract long getTotalTimeLocked(long elapsedRealtimeUs, int which);
/**
+ * Returns the total time in microseconds associated with this Timer since the
+ * 'mark' was last set.
+ *
+ * @param elapsedRealtimeUs current elapsed realtime of system in microseconds
+ * @return a time in microseconds
+ */
+ public abstract long getTimeSinceMarkLocked(long elapsedRealtimeUs);
+
+ /**
* Temporary for debugging.
*/
public abstract void logState(Printer pw, String prefix);
@@ -332,7 +341,17 @@ public abstract class BatteryStats implements Parcelable {
* @return a Map from Strings to Uid.Pkg objects.
*/
public abstract ArrayMap<String, ? extends Pkg> getPackageStats();
-
+
+ /**
+ * Returns the time in milliseconds that this app kept the WiFi controller in the
+ * specified state <code>type</code>.
+ * @param type one of {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, or
+ * {@link #CONTROLLER_TX_TIME}.
+ * @param which one of {@link #STATS_CURRENT}, {@link #STATS_SINCE_CHARGED}, or
+ * {@link #STATS_SINCE_UNPLUGGED}.
+ */
+ public abstract long getWifiControllerActivity(int type, int which);
+
/**
* {@hide}
*/
@@ -1914,7 +1933,6 @@ public abstract class BatteryStats implements Parcelable {
public static final int NETWORK_MOBILE_TX_DATA = 1;
public static final int NETWORK_WIFI_RX_DATA = 2;
public static final int NETWORK_WIFI_TX_DATA = 3;
-
public static final int NUM_NETWORK_ACTIVITY_TYPES = NETWORK_WIFI_TX_DATA + 1;
public abstract long getNetworkActivityBytes(int type, int which);
@@ -1923,10 +1941,25 @@ public abstract class BatteryStats implements Parcelable {
public static final int CONTROLLER_IDLE_TIME = 0;
public static final int CONTROLLER_RX_TIME = 1;
public static final int CONTROLLER_TX_TIME = 2;
- public static final int CONTROLLER_ENERGY = 3;
- public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_ENERGY + 1;
+ public static final int CONTROLLER_POWER_DRAIN = 3;
+ public static final int NUM_CONTROLLER_ACTIVITY_TYPES = CONTROLLER_POWER_DRAIN + 1;
+ /**
+ * For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and
+ * {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the
+ * respective state.
+ * For {@link #CONTROLLER_POWER_DRAIN}, returns the power used by the controller in
+ * milli-ampere-milliseconds (mAms).
+ */
public abstract long getBluetoothControllerActivity(int type, int which);
+
+ /**
+ * For {@link #CONTROLLER_IDLE_TIME}, {@link #CONTROLLER_RX_TIME}, and
+ * {@link #CONTROLLER_TX_TIME}, returns the time spent (in milliseconds) in the
+ * respective state.
+ * For {@link #CONTROLLER_POWER_DRAIN}, returns the power used by the controller in
+ * milli-ampere-milliseconds (mAms).
+ */
public abstract long getWifiControllerActivity(int type, int which);
/**
@@ -2618,7 +2651,7 @@ public abstract class BatteryStats implements Parcelable {
label = "???";
}
dumpLine(pw, uid, category, POWER_USE_ITEM_DATA, label,
- BatteryStatsHelper.makemAh(bs.value));
+ BatteryStatsHelper.makemAh(bs.totalPowerMah));
}
}
@@ -3233,9 +3266,9 @@ public abstract class BatteryStats implements Parcelable {
if (!didOne) sb.append(" (no activity)");
pw.println(sb.toString());
- final long wifiIdleTimeMs = getBluetoothControllerActivity(CONTROLLER_IDLE_TIME, which);
- final long wifiRxTimeMs = getBluetoothControllerActivity(CONTROLLER_RX_TIME, which);
- final long wifiTxTimeMs = getBluetoothControllerActivity(CONTROLLER_TX_TIME, which);
+ final long wifiIdleTimeMs = getWifiControllerActivity(CONTROLLER_IDLE_TIME, which);
+ final long wifiRxTimeMs = getWifiControllerActivity(CONTROLLER_RX_TIME, which);
+ final long wifiTxTimeMs = getWifiControllerActivity(CONTROLLER_TX_TIME, which);
final long wifiTotalTimeMs = wifiIdleTimeMs + wifiRxTimeMs + wifiTxTimeMs;
sb.setLength(0);
@@ -3264,6 +3297,13 @@ public abstract class BatteryStats implements Parcelable {
sb.setLength(0);
sb.append(prefix);
+ sb.append(" WiFi Energy use: ").append(BatteryStatsHelper.makemAh(
+ getWifiControllerActivity(CONTROLLER_POWER_DRAIN, which) / (double)(1000*60*60)));
+ sb.append(" mAh");
+ pw.println(sb.toString());
+
+ sb.setLength(0);
+ sb.append(prefix);
sb.append(" Bluetooth on: "); formatTimeMs(sb, bluetoothOnTime / 1000);
sb.append("("); sb.append(formatRatioLocked(bluetoothOnTime, whichBatteryRealtime));
sb.append(")");
@@ -3376,48 +3416,48 @@ public abstract class BatteryStats implements Parcelable {
final BatterySipper bs = sippers.get(i);
switch (bs.drainType) {
case IDLE:
- pw.print(prefix); pw.print(" Idle: "); printmAh(pw, bs.value);
+ pw.print(prefix); pw.print(" Idle: "); printmAh(pw, bs.totalPowerMah);
pw.println();
break;
case CELL:
- pw.print(prefix); pw.print(" Cell standby: "); printmAh(pw, bs.value);
+ pw.print(prefix); pw.print(" Cell standby: "); printmAh(pw, bs.totalPowerMah);
pw.println();
break;
case PHONE:
- pw.print(prefix); pw.print(" Phone calls: "); printmAh(pw, bs.value);
+ pw.print(prefix); pw.print(" Phone calls: "); printmAh(pw, bs.totalPowerMah);
pw.println();
break;
case WIFI:
- pw.print(prefix); pw.print(" Wifi: "); printmAh(pw, bs.value);
+ pw.print(prefix); pw.print(" Wifi: "); printmAh(pw, bs.totalPowerMah);
pw.println();
break;
case BLUETOOTH:
- pw.print(prefix); pw.print(" Bluetooth: "); printmAh(pw, bs.value);
+ pw.print(prefix); pw.print(" Bluetooth: "); printmAh(pw, bs.totalPowerMah);
pw.println();
break;
case SCREEN:
- pw.print(prefix); pw.print(" Screen: "); printmAh(pw, bs.value);
+ pw.print(prefix); pw.print(" Screen: "); printmAh(pw, bs.totalPowerMah);
pw.println();
break;
case FLASHLIGHT:
- pw.print(prefix); pw.print(" Flashlight: "); printmAh(pw, bs.value);
+ pw.print(prefix); pw.print(" Flashlight: "); printmAh(pw, bs.totalPowerMah);
pw.println();
break;
case APP:
pw.print(prefix); pw.print(" Uid ");
UserHandle.formatUid(pw, bs.uidObj.getUid());
- pw.print(": "); printmAh(pw, bs.value); pw.println();
+ pw.print(": "); printmAh(pw, bs.totalPowerMah); pw.println();
break;
case USER:
pw.print(prefix); pw.print(" User "); pw.print(bs.userId);
- pw.print(": "); printmAh(pw, bs.value); pw.println();
+ pw.print(": "); printmAh(pw, bs.totalPowerMah); pw.println();
break;
case UNACCOUNTED:
- pw.print(prefix); pw.print(" Unaccounted: "); printmAh(pw, bs.value);
+ pw.print(prefix); pw.print(" Unaccounted: "); printmAh(pw, bs.totalPowerMah);
pw.println();
break;
case OVERCOUNTED:
- pw.print(prefix); pw.print(" Over-counted: "); printmAh(pw, bs.value);
+ pw.print(prefix); pw.print(" Over-counted: "); printmAh(pw, bs.totalPowerMah);
pw.println();
break;
}
@@ -4415,7 +4455,7 @@ public abstract class BatteryStats implements Parcelable {
if (!checkin) {
pw.println(header);
}
- String[] lineArgs = new String[4];
+ String[] lineArgs = new String[5];
for (int i=0; i<count; i++) {
long duration = steps.getDurationAt(i);
int level = steps.getLevelAt(i);
@@ -4430,7 +4470,7 @@ public abstract class BatteryStats implements Parcelable {
case Display.STATE_ON: lineArgs[2] = "s+"; break;
case Display.STATE_DOZE: lineArgs[2] = "sd"; break;
case Display.STATE_DOZE_SUSPEND: lineArgs[2] = "sds"; break;
- default: lineArgs[1] = "?"; break;
+ default: lineArgs[2] = "?"; break;
}
} else {
lineArgs[2] = "";
@@ -4441,9 +4481,9 @@ public abstract class BatteryStats implements Parcelable {
lineArgs[3] = "";
}
if ((modMode&STEP_LEVEL_MODE_DEVICE_IDLE) == 0) {
- lineArgs[3] = (initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0 ? "i+" : "i-";
+ lineArgs[4] = (initMode&STEP_LEVEL_MODE_DEVICE_IDLE) != 0 ? "i+" : "i-";
} else {
- lineArgs[3] = "";
+ lineArgs[4] = "";
}
dumpLine(pw, 0 /* uid */, "i" /* category */, header, (Object[])lineArgs);
} else {
@@ -4459,7 +4499,7 @@ public abstract class BatteryStats implements Parcelable {
case Display.STATE_ON: pw.print("screen-on"); break;
case Display.STATE_DOZE: pw.print("screen-doze"); break;
case Display.STATE_DOZE_SUSPEND: pw.print("screen-doze-suspend"); break;
- default: lineArgs[1] = "screen-?"; break;
+ default: pw.print("screen-?"); break;
}
haveModes = true;
}
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 975bfc2..2eb97f1 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,16 +18,11 @@ package android.os;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
-import android.text.TextUtils;
import android.util.Log;
-import com.google.android.collect.Lists;
-
import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
/**
* Provides access to environment variables.
@@ -36,11 +31,9 @@ public class Environment {
private static final String TAG = "Environment";
private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
- private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE";
- private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
- private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE";
- private static final String ENV_SECONDARY_STORAGE = "SECONDARY_STORAGE";
private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
+ private static final String ENV_ANDROID_DATA = "ANDROID_DATA";
+ private static final String ENV_ANDROID_STORAGE = "ANDROID_STORAGE";
private static final String ENV_OEM_ROOT = "OEM_ROOT";
private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT";
@@ -57,12 +50,10 @@ public class Environment {
public static final String DIRECTORY_ANDROID = DIR_ANDROID;
private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
+ private static final File DIR_ANDROID_DATA = getDirectory(ENV_ANDROID_DATA, "/data");
+ private static final File DIR_ANDROID_STORAGE = getDirectory(ENV_ANDROID_STORAGE, "/storage");
private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem");
private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor");
- private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media");
-
- private static final String CANONCIAL_EMULATED_STORAGE_TARGET = getCanonicalPathOrNull(
- ENV_EMULATED_STORAGE_TARGET);
private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
@@ -81,73 +72,24 @@ public class Environment {
/** {@hide} */
public static class UserEnvironment {
- // TODO: generalize further to create package-specific environment
-
- /** External storage dirs, as visible to vold */
- private final File[] mExternalDirsForVold;
- /** External storage dirs, as visible to apps */
- private final File[] mExternalDirsForApp;
- /** Primary emulated storage dir for direct access */
- private final File mEmulatedDirForDirect;
+ private final int mUserId;
public UserEnvironment(int userId) {
- // See storage config details at http://source.android.com/tech/storage/
- String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
- String rawEmulatedSource = System.getenv(ENV_EMULATED_STORAGE_SOURCE);
- String rawEmulatedTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
-
- String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE);
- if (TextUtils.isEmpty(rawMediaStorage)) {
- rawMediaStorage = "/data/media";
- }
-
- ArrayList<File> externalForVold = Lists.newArrayList();
- ArrayList<File> externalForApp = Lists.newArrayList();
-
- if (!TextUtils.isEmpty(rawEmulatedTarget)) {
- // Device has emulated storage; external storage paths should have
- // userId burned into them.
- final String rawUserId = Integer.toString(userId);
- final File emulatedSourceBase = new File(rawEmulatedSource);
- final File emulatedTargetBase = new File(rawEmulatedTarget);
- final File mediaBase = new File(rawMediaStorage);
-
- // /storage/emulated/0
- externalForVold.add(buildPath(emulatedSourceBase, rawUserId));
- externalForApp.add(buildPath(emulatedTargetBase, rawUserId));
- // /data/media/0
- mEmulatedDirForDirect = buildPath(mediaBase, rawUserId);
-
- } else {
- // Device has physical external storage; use plain paths.
- if (TextUtils.isEmpty(rawExternalStorage)) {
- Log.w(TAG, "EXTERNAL_STORAGE undefined; falling back to default");
- rawExternalStorage = "/storage/sdcard0";
- }
-
- // /storage/sdcard0
- externalForVold.add(new File(rawExternalStorage));
- externalForApp.add(new File(rawExternalStorage));
- // /data/media
- mEmulatedDirForDirect = new File(rawMediaStorage);
- }
+ mUserId = userId;
+ }
- // Splice in any secondary storage paths, but only for owner
- final String rawSecondaryStorage = System.getenv(ENV_SECONDARY_STORAGE);
- if (!TextUtils.isEmpty(rawSecondaryStorage) && userId == UserHandle.USER_OWNER) {
- for (String secondaryPath : rawSecondaryStorage.split(":")) {
- externalForVold.add(new File(secondaryPath));
- externalForApp.add(new File(secondaryPath));
- }
+ public File[] getExternalDirs() {
+ final StorageVolume[] volumes = StorageManager.getVolumeList(mUserId);
+ final File[] files = new File[volumes.length];
+ for (int i = 0; i < volumes.length; i++) {
+ files[i] = volumes[i].getPathFile();
}
-
- mExternalDirsForVold = externalForVold.toArray(new File[externalForVold.size()]);
- mExternalDirsForApp = externalForApp.toArray(new File[externalForApp.size()]);
+ return files;
}
@Deprecated
public File getExternalStorageDirectory() {
- return mExternalDirsForApp[0];
+ return getExternalDirs()[0];
}
@Deprecated
@@ -155,60 +97,36 @@ public class Environment {
return buildExternalStoragePublicDirs(type)[0];
}
- public File[] getExternalDirsForVold() {
- return mExternalDirsForVold;
- }
-
- public File[] getExternalDirsForApp() {
- return mExternalDirsForApp;
- }
-
- public File getMediaDir() {
- return mEmulatedDirForDirect;
- }
-
public File[] buildExternalStoragePublicDirs(String type) {
- return buildPaths(mExternalDirsForApp, type);
+ return buildPaths(getExternalDirs(), type);
}
public File[] buildExternalStorageAndroidDataDirs() {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA);
}
public File[] buildExternalStorageAndroidObbDirs() {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB);
}
public File[] buildExternalStorageAppDataDirs(String packageName) {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName);
- }
-
- public File[] buildExternalStorageAppDataDirsForVold(String packageName) {
- return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_DATA, packageName);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName);
}
public File[] buildExternalStorageAppMediaDirs(String packageName) {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName);
- }
-
- public File[] buildExternalStorageAppMediaDirsForVold(String packageName) {
- return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_MEDIA, packageName);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_MEDIA, packageName);
}
public File[] buildExternalStorageAppObbDirs(String packageName) {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName);
- }
-
- public File[] buildExternalStorageAppObbDirsForVold(String packageName) {
- return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_OBB, packageName);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB, packageName);
}
public File[] buildExternalStorageAppFilesDirs(String packageName) {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
}
public File[] buildExternalStorageAppCacheDirs(String packageName) {
- return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
+ return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
}
}
@@ -220,6 +138,11 @@ public class Environment {
return DIR_ANDROID_ROOT;
}
+ /** {@hide} */
+ public static File getStorageDirectory() {
+ return DIR_ANDROID_STORAGE;
+ }
+
/**
* Return root directory of the "oem" partition holding OEM customizations,
* if any. If present, the partition is mounted read-only.
@@ -270,17 +193,6 @@ public class Environment {
}
/**
- * Return directory used for internal media storage, which is protected by
- * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
- *
- * @hide
- */
- public static File getMediaStorageDirectory() {
- throwIfUserRequired();
- return sCurrentUser.getMediaDir();
- }
-
- /**
* Return the system directory for a user. This is for use by system services to store
* files relating to the user. This directory will be automatically deleted when the user
* is removed.
@@ -389,7 +301,7 @@ public class Environment {
*/
public static File getExternalStorageDirectory() {
throwIfUserRequired();
- return sCurrentUser.getExternalDirsForApp()[0];
+ return sCurrentUser.getExternalDirs()[0];
}
/** {@hide} */
@@ -402,18 +314,6 @@ public class Environment {
return buildPath(getLegacyExternalStorageDirectory(), DIR_ANDROID, DIR_OBB);
}
- /** {@hide} */
- public static File getEmulatedStorageSource(int userId) {
- // /mnt/shell/emulated/0
- return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), String.valueOf(userId));
- }
-
- /** {@hide} */
- public static File getEmulatedStorageObbSource() {
- // /mnt/shell/emulated/obb
- return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), DIR_OBB);
- }
-
/**
* Standard directory in which to place any audio files that should be
* in the regular list of music for the user.
@@ -683,6 +583,13 @@ public class Environment {
public static final String MEDIA_UNMOUNTABLE = "unmountable";
/**
+ * Storage state if the media is in the process of being ejected.
+ *
+ * @see #getExternalStorageState(File)
+ */
+ public static final String MEDIA_EJECTING = "ejecting";
+
+ /**
* Returns the current state of the primary "external" storage device.
*
* @see #getExternalStorageDirectory()
@@ -693,7 +600,7 @@ public class Environment {
* {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
*/
public static String getExternalStorageState() {
- final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+ final File externalDir = sCurrentUser.getExternalDirs()[0];
return getExternalStorageState(externalDir);
}
@@ -716,17 +623,12 @@ public class Environment {
* {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
*/
public static String getExternalStorageState(File path) {
- final StorageVolume volume = getStorageVolume(path);
+ final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
if (volume != null) {
- final IMountService mountService = IMountService.Stub.asInterface(
- ServiceManager.getService("mount"));
- try {
- return mountService.getVolumeState(volume.getPath());
- } catch (RemoteException e) {
- }
+ return volume.getState();
+ } else {
+ return MEDIA_UNKNOWN;
}
-
- return Environment.MEDIA_UNKNOWN;
}
/**
@@ -738,7 +640,7 @@ public class Environment {
*/
public static boolean isExternalStorageRemovable() {
if (isStorageDisabled()) return false;
- final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+ final File externalDir = sCurrentUser.getExternalDirs()[0];
return isExternalStorageRemovable(externalDir);
}
@@ -753,7 +655,7 @@ public class Environment {
* device.
*/
public static boolean isExternalStorageRemovable(File path) {
- final StorageVolume volume = getStorageVolume(path);
+ final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
if (volume != null) {
return volume.isRemovable();
} else {
@@ -771,7 +673,7 @@ public class Environment {
*/
public static boolean isExternalStorageEmulated() {
if (isStorageDisabled()) return false;
- final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+ final File externalDir = sCurrentUser.getExternalDirs()[0];
return isExternalStorageEmulated(externalDir);
}
@@ -784,7 +686,7 @@ public class Environment {
* device.
*/
public static boolean isExternalStorageEmulated(File path) {
- final StorageVolume volume = getStorageVolume(path);
+ final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
if (volume != null) {
return volume.isEmulated();
} else {
@@ -797,19 +699,6 @@ public class Environment {
return path == null ? new File(defaultPath) : new File(path);
}
- private static String getCanonicalPathOrNull(String variableName) {
- String path = System.getenv(variableName);
- if (path == null) {
- return null;
- }
- try {
- return new File(path).getCanonicalPath();
- } catch (IOException e) {
- Log.w(TAG, "Unable to resolve canonical path for " + path);
- return null;
- }
- }
-
/** {@hide} */
public static void setUserRequired(boolean userRequired) {
sUserRequired = userRequired;
@@ -856,28 +745,6 @@ public class Environment {
return SystemProperties.getBoolean("config.disable_storage", false);
}
- private static StorageVolume getStorageVolume(File path) {
- try {
- path = path.getCanonicalFile();
- } catch (IOException e) {
- return null;
- }
-
- try {
- final IMountService mountService = IMountService.Stub.asInterface(
- ServiceManager.getService("mount"));
- final StorageVolume[] volumes = mountService.getVolumeList();
- for (StorageVolume volume : volumes) {
- if (FileUtils.contains(volume.getPathFile(), path)) {
- return volume;
- }
- }
- } catch (RemoteException e) {
- }
-
- return null;
- }
-
/**
* If the given path exists on emulated external storage, return the
* translated backing path hosted on internal storage. This bypasses any
@@ -891,26 +758,7 @@ public class Environment {
* @hide
*/
public static File maybeTranslateEmulatedPathToInternal(File path) {
- // Fast return if not emulated, or missing variables
- if (!Environment.isExternalStorageEmulated()
- || CANONCIAL_EMULATED_STORAGE_TARGET == null) {
- return path;
- }
-
- try {
- final String rawPath = path.getCanonicalPath();
- if (rawPath.startsWith(CANONCIAL_EMULATED_STORAGE_TARGET)) {
- final File internalPath = new File(DIR_MEDIA_STORAGE,
- rawPath.substring(CANONCIAL_EMULATED_STORAGE_TARGET.length()));
- if (internalPath.exists()) {
- return internalPath;
- }
- }
- } catch (IOException e) {
- Log.w(TAG, "Failed to resolve canonical path for " + path);
- }
-
- // Unable to translate to internal path; use original
+ // TODO: bring back this optimization
return path;
}
}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 0a724a1..b302f95 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -369,6 +369,23 @@ public class FileUtils {
* {@link File#getCanonicalFile()} to avoid symlink or path traversal
* attacks.
*/
+ public static boolean contains(File[] dirs, File file) {
+ for (File dir : dirs) {
+ if (contains(dir, file)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Test if a file lives under the given directory, either as a direct child
+ * or a distant grandchild.
+ * <p>
+ * Both files <em>must</em> have been resolved using
+ * {@link File#getCanonicalFile()} to avoid symlink or path traversal
+ * attacks.
+ */
public static boolean contains(File dir, File file) {
if (file == null) return false;
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 0de9c70..355ec8c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -637,10 +637,8 @@ public class Process {
if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
argsForZygote.add("--enable-assert");
}
- if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER) {
- argsForZygote.add("--mount-external-multiuser");
- } else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) {
- argsForZygote.add("--mount-external-multiuser-all");
+ if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
+ argsForZygote.add("--mount-external-default");
}
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
diff --git a/core/java/android/os/storage/DiskInfo.aidl b/core/java/android/os/storage/DiskInfo.aidl
new file mode 100644
index 0000000..5126c19
--- /dev/null
+++ b/core/java/android/os/storage/DiskInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+parcelable DiskInfo;
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java
new file mode 100644
index 0000000..56c55f1
--- /dev/null
+++ b/core/java/android/os/storage/DiskInfo.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.content.Context;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DebugUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.io.CharArrayWriter;
+
+/**
+ * Information about a physical disk which may contain one or more
+ * {@link VolumeInfo}.
+ *
+ * @hide
+ */
+public class DiskInfo implements Parcelable {
+ public static final int FLAG_ADOPTABLE = 1 << 0;
+ public static final int FLAG_DEFAULT_PRIMARY = 1 << 1;
+ public static final int FLAG_SD = 1 << 2;
+ public static final int FLAG_USB = 1 << 3;
+
+ public final String id;
+ public final int flags;
+ public long size;
+ public String label;
+ public String[] volumes;
+
+ public DiskInfo(String id, int flags) {
+ this.id = Preconditions.checkNotNull(id);
+ this.flags = flags;
+ }
+
+ public DiskInfo(Parcel parcel) {
+ id = parcel.readString();
+ flags = parcel.readInt();
+ size = parcel.readLong();
+ label = parcel.readString();
+ volumes = parcel.readStringArray();
+ }
+
+ public String getDescription(Context context) {
+ // TODO: splice vendor label into these strings
+ if ((flags & FLAG_SD) != 0) {
+ return context.getString(com.android.internal.R.string.storage_sd_card);
+ } else if ((flags & FLAG_USB) != 0) {
+ return context.getString(com.android.internal.R.string.storage_usb);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ final CharArrayWriter writer = new CharArrayWriter();
+ dump(new IndentingPrintWriter(writer, " ", 80));
+ return writer.toString();
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("DiskInfo:");
+ pw.increaseIndent();
+ pw.printPair("id", id);
+ pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
+ pw.printPair("size", size);
+ pw.printPair("label", label);
+ pw.printPair("volumes", volumes);
+ pw.decreaseIndent();
+ pw.println();
+ }
+
+ @Override
+ public DiskInfo clone() {
+ final Parcel temp = Parcel.obtain();
+ try {
+ writeToParcel(temp, 0);
+ temp.setDataPosition(0);
+ return CREATOR.createFromParcel(temp);
+ } finally {
+ temp.recycle();
+ }
+ }
+
+ public static final Creator<DiskInfo> CREATOR = new Creator<DiskInfo>() {
+ @Override
+ public DiskInfo createFromParcel(Parcel in) {
+ return new DiskInfo(in);
+ }
+
+ @Override
+ public DiskInfo[] newArray(int size) {
+ return new DiskInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(id);
+ parcel.writeInt(flags);
+ parcel.writeLong(size);
+ parcel.writeString(label);
+ parcel.writeStringArray(volumes);
+ }
+}
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 6209c2a..10ffd48 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -757,12 +757,13 @@ public interface IMountService extends IInterface {
return _result;
}
- public StorageVolume[] getVolumeList() throws RemoteException {
+ public StorageVolume[] getVolumeList(int userId) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
StorageVolume[] _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeInt(userId);
mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArray(StorageVolume.CREATOR);
@@ -903,6 +904,131 @@ public interface IMountService extends IInterface {
}
return;
}
+
+ @Override
+ public DiskInfo[] getDisks() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ DiskInfo[] _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_getDisks, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.createTypedArray(DiskInfo.CREATOR);
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ @Override
+ public VolumeInfo[] getVolumes() throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ VolumeInfo[] _result;
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ mRemote.transact(Stub.TRANSACTION_getVolumes, _data, _reply, 0);
+ _reply.readException();
+ _result = _reply.createTypedArray(VolumeInfo.CREATOR);
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ return _result;
+ }
+
+ @Override
+ public void mount(String volId) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(volId);
+ mRemote.transact(Stub.TRANSACTION_mount, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ @Override
+ public void unmount(String volId) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(volId);
+ mRemote.transact(Stub.TRANSACTION_unmount, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ @Override
+ public void format(String volId) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(volId);
+ mRemote.transact(Stub.TRANSACTION_format, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ @Override
+ public void partitionPublic(String diskId) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(diskId);
+ mRemote.transact(Stub.TRANSACTION_partitionPublic, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ @Override
+ public void partitionPrivate(String diskId) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(diskId);
+ mRemote.transact(Stub.TRANSACTION_partitionPrivate, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ @Override
+ public void partitionMixed(String diskId, int ratio) throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeString(diskId);
+ _data.writeInt(ratio);
+ mRemote.transact(Stub.TRANSACTION_partitionMixed, _data, _reply, 0);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
}
private static final String DESCRIPTOR = "IMountService";
@@ -995,6 +1121,17 @@ public interface IMountService extends IInterface {
static final int TRANSACTION_waitForAsecScan = IBinder.FIRST_CALL_TRANSACTION + 43;
+ static final int TRANSACTION_getDisks = IBinder.FIRST_CALL_TRANSACTION + 44;
+ static final int TRANSACTION_getVolumes = IBinder.FIRST_CALL_TRANSACTION + 45;
+
+ static final int TRANSACTION_mount = IBinder.FIRST_CALL_TRANSACTION + 46;
+ static final int TRANSACTION_unmount = IBinder.FIRST_CALL_TRANSACTION + 47;
+ static final int TRANSACTION_format = IBinder.FIRST_CALL_TRANSACTION + 48;
+
+ static final int TRANSACTION_partitionPublic = IBinder.FIRST_CALL_TRANSACTION + 49;
+ static final int TRANSACTION_partitionPrivate = IBinder.FIRST_CALL_TRANSACTION + 50;
+ static final int TRANSACTION_partitionMixed = IBinder.FIRST_CALL_TRANSACTION + 51;
+
/**
* Cast an IBinder object into an IMountService interface, generating a
* proxy if needed.
@@ -1308,7 +1445,8 @@ public interface IMountService extends IInterface {
}
case TRANSACTION_getVolumeList: {
data.enforceInterface(DESCRIPTOR);
- StorageVolume[] result = getVolumeList();
+ int userId = data.readInt();
+ StorageVolume[] result = getVolumeList(userId);
reply.writeNoException();
reply.writeTypedArray(result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
return true;
@@ -1419,6 +1557,63 @@ public interface IMountService extends IInterface {
reply.writeNoException();
return true;
}
+ case TRANSACTION_getDisks: {
+ data.enforceInterface(DESCRIPTOR);
+ DiskInfo[] disks = getDisks();
+ reply.writeNoException();
+ reply.writeTypedArray(disks, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ return true;
+ }
+ case TRANSACTION_getVolumes: {
+ data.enforceInterface(DESCRIPTOR);
+ VolumeInfo[] volumes = getVolumes();
+ reply.writeNoException();
+ reply.writeTypedArray(volumes, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ return true;
+ }
+ case TRANSACTION_mount: {
+ data.enforceInterface(DESCRIPTOR);
+ String volId = data.readString();
+ mount(volId);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_unmount: {
+ data.enforceInterface(DESCRIPTOR);
+ String volId = data.readString();
+ unmount(volId);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_format: {
+ data.enforceInterface(DESCRIPTOR);
+ String volId = data.readString();
+ format(volId);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_partitionPublic: {
+ data.enforceInterface(DESCRIPTOR);
+ String diskId = data.readString();
+ partitionPublic(diskId);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_partitionPrivate: {
+ data.enforceInterface(DESCRIPTOR);
+ String diskId = data.readString();
+ partitionPrivate(diskId);
+ reply.writeNoException();
+ return true;
+ }
+ case TRANSACTION_partitionMixed: {
+ data.enforceInterface(DESCRIPTOR);
+ String diskId = data.readString();
+ int ratio = data.readInt();
+ partitionMixed(diskId, ratio);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
}
@@ -1630,7 +1825,7 @@ public interface IMountService extends IInterface {
/**
* Returns list of all mountable volumes.
*/
- public StorageVolume[] getVolumeList() throws RemoteException;
+ public StorageVolume[] getVolumeList(int userId) throws RemoteException;
/**
* Gets the path on the filesystem for the ASEC container itself.
@@ -1705,4 +1900,15 @@ public interface IMountService extends IInterface {
public void runMaintenance() throws RemoteException;
public void waitForAsecScan() throws RemoteException;
+
+ public DiskInfo[] getDisks() throws RemoteException;
+ public VolumeInfo[] getVolumes() throws RemoteException;
+
+ public void mount(String volId) throws RemoteException;
+ public void unmount(String volId) throws RemoteException;
+ public void format(String volId) throws RemoteException;
+
+ public void partitionPublic(String diskId) throws RemoteException;
+ public void partitionPrivate(String diskId) throws RemoteException;
+ public void partitionMixed(String diskId, int ratio) throws RemoteException;
}
diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java
index d5c5fa5..3965f9d 100644
--- a/core/java/android/os/storage/IMountServiceListener.java
+++ b/core/java/android/os/storage/IMountServiceListener.java
@@ -75,16 +75,22 @@ public interface IMountServiceListener extends IInterface {
}
case TRANSACTION_onStorageStateChanged: {
data.enforceInterface(DESCRIPTOR);
- String path;
- path = data.readString();
- String oldState;
- oldState = data.readString();
- String newState;
- newState = data.readString();
+ final String path = data.readString();
+ final String oldState = data.readString();
+ final String newState = data.readString();
this.onStorageStateChanged(path, oldState, newState);
reply.writeNoException();
return true;
}
+ case TRANSACTION_onVolumeStateChanged: {
+ data.enforceInterface(DESCRIPTOR);
+ final VolumeInfo vol = (VolumeInfo) data.readParcelable(null);
+ final int oldState = data.readInt();
+ final int newState = data.readInt();
+ onVolumeStateChanged(vol, oldState, newState);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
}
@@ -116,7 +122,7 @@ public interface IMountServiceListener extends IInterface {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(((connected) ? (1) : (0)));
mRemote.transact(Stub.TRANSACTION_onUsbMassStorageConnectionChanged, _data,
- _reply, 0);
+ _reply, android.os.IBinder.FLAG_ONEWAY);
_reply.readException();
} finally {
_reply.recycle();
@@ -142,7 +148,27 @@ public interface IMountServiceListener extends IInterface {
_data.writeString(path);
_data.writeString(oldState);
_data.writeString(newState);
- mRemote.transact(Stub.TRANSACTION_onStorageStateChanged, _data, _reply, 0);
+ mRemote.transact(Stub.TRANSACTION_onStorageStateChanged, _data, _reply,
+ android.os.IBinder.FLAG_ONEWAY);
+ _reply.readException();
+ } finally {
+ _reply.recycle();
+ _data.recycle();
+ }
+ }
+
+ @Override
+ public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState)
+ throws RemoteException {
+ Parcel _data = Parcel.obtain();
+ Parcel _reply = Parcel.obtain();
+ try {
+ _data.writeInterfaceToken(DESCRIPTOR);
+ _data.writeParcelable(vol, 0);
+ _data.writeInt(oldState);
+ _data.writeInt(newState);
+ mRemote.transact(Stub.TRANSACTION_onVolumeStateChanged, _data, _reply,
+ android.os.IBinder.FLAG_ONEWAY);
_reply.readException();
} finally {
_reply.recycle();
@@ -154,6 +180,8 @@ public interface IMountServiceListener extends IInterface {
static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_onStorageStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 1);
+
+ static final int TRANSACTION_onVolumeStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 2);
}
/**
@@ -173,4 +201,7 @@ public interface IMountServiceListener extends IInterface {
*/
public void onStorageStateChanged(String path, String oldState, String newState)
throws RemoteException;
+
+ public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState)
+ throws RemoteException;
}
diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java
index 6c73d04..29d5494 100644
--- a/core/java/android/os/storage/StorageEventListener.java
+++ b/core/java/android/os/storage/StorageEventListener.java
@@ -21,7 +21,7 @@ package android.os.storage;
*
* @hide
*/
-public abstract class StorageEventListener {
+public class StorageEventListener {
/**
* Called when the detection state of a USB Mass Storage host has changed.
* @param connected true if the USB mass storage is connected.
@@ -37,4 +37,7 @@ public abstract class StorageEventListener {
*/
public void onStorageStateChanged(String path, String oldState, String newState) {
}
+
+ public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+ }
}
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2785ee8..5a5d8b7 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -18,26 +18,32 @@ package android.os.storage;
import static android.net.TrafficStats.MB_IN_BYTES;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -60,46 +66,70 @@ import java.util.concurrent.atomic.AtomicInteger;
public class StorageManager {
private static final String TAG = "StorageManager";
+ /** {@hide} */
+ public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
+
+ private final Context mContext;
private final ContentResolver mResolver;
- /*
- * Our internal MountService binder reference
- */
private final IMountService mMountService;
+ private final Looper mLooper;
+ private final AtomicInteger mNextNonce = new AtomicInteger(0);
- /*
- * The looper target for callbacks
- */
- private final Looper mTgtLooper;
+ private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
- /*
- * Target listener for binder callbacks
- */
- private MountServiceBinderListener mBinderListener;
+ private static class StorageEventListenerDelegate extends IMountServiceListener.Stub implements
+ Handler.Callback {
+ private static final int MSG_STORAGE_STATE_CHANGED = 1;
+ private static final int MSG_VOLUME_STATE_CHANGED = 2;
- /*
- * List of our listeners
- */
- private List<ListenerDelegate> mListeners = new ArrayList<ListenerDelegate>();
+ final StorageEventListener mCallback;
+ final Handler mHandler;
- /*
- * Next available nonce
- */
- final private AtomicInteger mNextNonce = new AtomicInteger(0);
+ public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) {
+ mCallback = callback;
+ mHandler = new Handler(looper, this);
+ }
- private class MountServiceBinderListener extends IMountServiceListener.Stub {
- public void onUsbMassStorageConnectionChanged(boolean available) {
- final int size = mListeners.size();
- for (int i = 0; i < size; i++) {
- mListeners.get(i).sendShareAvailabilityChanged(available);
+ @Override
+ public boolean handleMessage(Message msg) {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ switch (msg.what) {
+ case MSG_STORAGE_STATE_CHANGED:
+ mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
+ (String) args.arg3);
+ args.recycle();
+ return true;
+ case MSG_VOLUME_STATE_CHANGED:
+ mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
+ args.recycle();
+ return true;
}
+ args.recycle();
+ return false;
+ }
+
+ @Override
+ public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
+ // Ignored
}
+ @Override
public void onStorageStateChanged(String path, String oldState, String newState) {
- final int size = mListeners.size();
- for (int i = 0; i < size; i++) {
- mListeners.get(i).sendStorageStateChanged(path, oldState, newState);
- }
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = path;
+ args.arg2 = oldState;
+ args.arg3 = newState;
+ mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
+ }
+
+ @Override
+ public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = vol;
+ args.argi2 = oldState;
+ args.argi3 = newState;
+ mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
}
}
@@ -154,7 +184,7 @@ public class StorageManager {
ObbListenerDelegate(OnObbStateChangeListener listener) {
nonce = getNextNonce();
mObbEventListenerRef = new WeakReference<OnObbStateChangeListener>(listener);
- mHandler = new Handler(mTgtLooper) {
+ mHandler = new Handler(mLooper) {
@Override
public void handleMessage(Message msg) {
final OnObbStateChangeListener changeListener = getListener();
@@ -162,14 +192,7 @@ public class StorageManager {
return;
}
- StorageEvent e = (StorageEvent) msg.obj;
-
- if (msg.what == StorageEvent.EVENT_OBB_STATE_CHANGED) {
- ObbStateChangedStorageEvent ev = (ObbStateChangedStorageEvent) e;
- changeListener.onObbStateChange(ev.path, ev.state);
- } else {
- Log.e(TAG, "Unsupported event " + msg.what);
- }
+ changeListener.onObbStateChange((String) msg.obj, msg.arg1);
}
};
}
@@ -182,115 +205,7 @@ public class StorageManager {
}
void sendObbStateChanged(String path, int state) {
- ObbStateChangedStorageEvent e = new ObbStateChangedStorageEvent(path, state);
- mHandler.sendMessage(e.getMessage());
- }
- }
-
- /**
- * Message sent during an OBB status change event.
- */
- private class ObbStateChangedStorageEvent extends StorageEvent {
- public final String path;
-
- public final int state;
-
- public ObbStateChangedStorageEvent(String path, int state) {
- super(EVENT_OBB_STATE_CHANGED);
- this.path = path;
- this.state = state;
- }
- }
-
- /**
- * Private base class for messages sent between the callback thread
- * and the target looper handler.
- */
- private class StorageEvent {
- static final int EVENT_UMS_CONNECTION_CHANGED = 1;
- static final int EVENT_STORAGE_STATE_CHANGED = 2;
- static final int EVENT_OBB_STATE_CHANGED = 3;
-
- private Message mMessage;
-
- public StorageEvent(int what) {
- mMessage = Message.obtain();
- mMessage.what = what;
- mMessage.obj = this;
- }
-
- public Message getMessage() {
- return mMessage;
- }
- }
-
- /**
- * Message sent on a USB mass storage connection change.
- */
- private class UmsConnectionChangedStorageEvent extends StorageEvent {
- public boolean available;
-
- public UmsConnectionChangedStorageEvent(boolean a) {
- super(EVENT_UMS_CONNECTION_CHANGED);
- available = a;
- }
- }
-
- /**
- * Message sent on volume state change.
- */
- private class StorageStateChangedStorageEvent extends StorageEvent {
- public String path;
- public String oldState;
- public String newState;
-
- public StorageStateChangedStorageEvent(String p, String oldS, String newS) {
- super(EVENT_STORAGE_STATE_CHANGED);
- path = p;
- oldState = oldS;
- newState = newS;
- }
- }
-
- /**
- * Private class containing sender and receiver code for StorageEvents.
- */
- private class ListenerDelegate {
- final StorageEventListener mStorageEventListener;
- private final Handler mHandler;
-
- ListenerDelegate(StorageEventListener listener) {
- mStorageEventListener = listener;
- mHandler = new Handler(mTgtLooper) {
- @Override
- public void handleMessage(Message msg) {
- StorageEvent e = (StorageEvent) msg.obj;
-
- if (msg.what == StorageEvent.EVENT_UMS_CONNECTION_CHANGED) {
- UmsConnectionChangedStorageEvent ev = (UmsConnectionChangedStorageEvent) e;
- mStorageEventListener.onUsbMassStorageConnectionChanged(ev.available);
- } else if (msg.what == StorageEvent.EVENT_STORAGE_STATE_CHANGED) {
- StorageStateChangedStorageEvent ev = (StorageStateChangedStorageEvent) e;
- mStorageEventListener.onStorageStateChanged(ev.path, ev.oldState, ev.newState);
- } else {
- Log.e(TAG, "Unsupported event " + msg.what);
- }
- }
- };
- }
-
- StorageEventListener getListener() {
- return mStorageEventListener;
- }
-
- void sendShareAvailabilityChanged(boolean available) {
- UmsConnectionChangedStorageEvent e = new UmsConnectionChangedStorageEvent(available);
- mHandler.sendMessage(e.getMessage());
- }
-
- void sendStorageStateChanged(String path, String oldState, String newState) {
- StorageStateChangedStorageEvent e = new StorageStateChangedStorageEvent(path, oldState, newState);
- mHandler.sendMessage(e.getMessage());
+ mHandler.obtainMessage(0, state, 0, path).sendToTarget();
}
}
@@ -311,13 +226,13 @@ public class StorageManager {
*
* @hide
*/
- public StorageManager(ContentResolver resolver, Looper tgtLooper) throws RemoteException {
- mResolver = resolver;
- mTgtLooper = tgtLooper;
+ public StorageManager(Context context, Looper looper) {
+ mContext = context;
+ mResolver = context.getContentResolver();
+ mLooper = looper;
mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
if (mMountService == null) {
- Log.e(TAG, "Unable to connect to mount service! - is it running yet?");
- return;
+ throw new IllegalStateException("Failed to find running mount service");
}
}
@@ -329,21 +244,15 @@ public class StorageManager {
* @hide
*/
public void registerListener(StorageEventListener listener) {
- if (listener == null) {
- return;
- }
-
- synchronized (mListeners) {
- if (mBinderListener == null ) {
- try {
- mBinderListener = new MountServiceBinderListener();
- mMountService.registerListener(mBinderListener);
- } catch (RemoteException rex) {
- Log.e(TAG, "Register mBinderListener failed");
- return;
- }
+ synchronized (mDelegates) {
+ final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener,
+ mLooper);
+ try {
+ mMountService.registerListener(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
- mListeners.add(new ListenerDelegate(listener));
+ mDelegates.add(delegate);
}
}
@@ -355,28 +264,19 @@ public class StorageManager {
* @hide
*/
public void unregisterListener(StorageEventListener listener) {
- if (listener == null) {
- return;
- }
-
- synchronized (mListeners) {
- final int size = mListeners.size();
- for (int i=0 ; i<size ; i++) {
- ListenerDelegate l = mListeners.get(i);
- if (l.getListener() == listener) {
- mListeners.remove(i);
- break;
- }
- }
- if (mListeners.size() == 0 && mBinderListener != null) {
- try {
- mMountService.unregisterListener(mBinderListener);
- } catch (RemoteException rex) {
- Log.e(TAG, "Unregister mBinderListener failed");
- return;
+ synchronized (mDelegates) {
+ for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
+ final StorageEventListenerDelegate delegate = i.next();
+ if (delegate.mCallback == listener) {
+ try {
+ mMountService.unregisterListener(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ i.remove();
}
}
- }
+ }
}
/**
@@ -384,12 +284,8 @@ public class StorageManager {
*
* @hide
*/
+ @Deprecated
public void enableUsbMassStorage() {
- try {
- mMountService.setUsbMassStorageEnabled(true);
- } catch (Exception ex) {
- Log.e(TAG, "Failed to enable UMS", ex);
- }
}
/**
@@ -397,12 +293,8 @@ public class StorageManager {
*
* @hide
*/
+ @Deprecated
public void disableUsbMassStorage() {
- try {
- mMountService.setUsbMassStorageEnabled(false);
- } catch (Exception ex) {
- Log.e(TAG, "Failed to disable UMS", ex);
- }
}
/**
@@ -411,12 +303,8 @@ public class StorageManager {
*
* @hide
*/
+ @Deprecated
public boolean isUsbMassStorageConnected() {
- try {
- return mMountService.isUsbMassStorageConnected();
- } catch (Exception ex) {
- Log.e(TAG, "Failed to get UMS connection state", ex);
- }
return false;
}
@@ -426,12 +314,8 @@ public class StorageManager {
*
* @hide
*/
+ @Deprecated
public boolean isUsbMassStorageEnabled() {
- try {
- return mMountService.isUsbMassStorageEnabled();
- } catch (RemoteException rex) {
- Log.e(TAG, "Failed to get UMS enable state", rex);
- }
return false;
}
@@ -548,38 +432,170 @@ public class StorageManager {
return null;
}
- /**
- * Gets the state of a volume via its mountpoint.
- * @hide
- */
- public String getVolumeState(String mountPoint) {
- if (mMountService == null) return Environment.MEDIA_REMOVED;
+ /** {@hide} */
+ public @NonNull List<DiskInfo> getDisks() {
try {
- return mMountService.getVolumeState(mountPoint);
+ return Arrays.asList(mMountService.getDisks());
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get volume state", e);
- return null;
+ throw e.rethrowAsRuntimeException();
}
}
+ /** {@hide} */
+ public @Nullable DiskInfo findDiskById(String id) {
+ Preconditions.checkNotNull(id);
+ // TODO; go directly to service to make this faster
+ for (DiskInfo disk : getDisks()) {
+ if (Objects.equals(disk.id, id)) {
+ return disk;
+ }
+ }
+ return null;
+ }
+
+ /** {@hide} */
+ public @Nullable VolumeInfo findVolumeById(String id) {
+ Preconditions.checkNotNull(id);
+ // TODO; go directly to service to make this faster
+ for (VolumeInfo vol : getVolumes()) {
+ if (Objects.equals(vol.id, id)) {
+ return vol;
+ }
+ }
+ return null;
+ }
+
+ /** {@hide} */
+ public @Nullable VolumeInfo findVolumeByUuid(String fsUuid) {
+ Preconditions.checkNotNull(fsUuid);
+ // TODO; go directly to service to make this faster
+ for (VolumeInfo vol : getVolumes()) {
+ if (Objects.equals(vol.fsUuid, fsUuid)) {
+ return vol;
+ }
+ }
+ return null;
+ }
+
+ /** {@hide} */
+ public @NonNull List<VolumeInfo> getVolumes() {
+ try {
+ return Arrays.asList(mMountService.getVolumes());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
+ public void mount(String volId) {
+ try {
+ mMountService.mount(volId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
+ public void unmount(String volId) {
+ try {
+ mMountService.unmount(volId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
+ public void format(String volId) {
+ try {
+ mMountService.format(volId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
+ public void partitionPublic(String diskId) {
+ try {
+ mMountService.partitionPublic(diskId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
+ public void partitionPrivate(String diskId) {
+ try {
+ mMountService.partitionPrivate(diskId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
+ public void partitionMixed(String diskId, int ratio) {
+ try {
+ mMountService.partitionMixed(diskId, ratio);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
+ public @Nullable StorageVolume getStorageVolume(File file) {
+ return getStorageVolume(getVolumeList(), file);
+ }
+
+ /** {@hide} */
+ public static @Nullable StorageVolume getStorageVolume(File file, int userId) {
+ return getStorageVolume(getVolumeList(userId), file);
+ }
+
+ /** {@hide} */
+ private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) {
+ File canonicalFile = null;
+ try {
+ canonicalFile = file.getCanonicalFile();
+ } catch (IOException ignored) {
+ canonicalFile = null;
+ }
+ for (StorageVolume volume : volumes) {
+ if (volume.getPathFile().equals(file)) {
+ return volume;
+ }
+ if (FileUtils.contains(volume.getPathFile(), canonicalFile)) {
+ return volume;
+ }
+ }
+ return null;
+ }
+
/**
- * Returns list of all mountable volumes.
+ * Gets the state of a volume via its mountpoint.
* @hide
*/
- public StorageVolume[] getVolumeList() {
- if (mMountService == null) return new StorageVolume[0];
+ @Deprecated
+ public @NonNull String getVolumeState(String mountPoint) {
+ final StorageVolume vol = getStorageVolume(new File(mountPoint));
+ if (vol != null) {
+ return vol.getState();
+ } else {
+ return Environment.MEDIA_UNKNOWN;
+ }
+ }
+
+ /** {@hide} */
+ public @NonNull StorageVolume[] getVolumeList() {
+ return getVolumeList(mContext.getUserId());
+ }
+
+ /** {@hide} */
+ public static @NonNull StorageVolume[] getVolumeList(int userId) {
+ final IMountService mountService = IMountService.Stub.asInterface(
+ ServiceManager.getService("mount"));
try {
- Parcelable[] list = mMountService.getVolumeList();
- if (list == null) return new StorageVolume[0];
- int length = list.length;
- StorageVolume[] result = new StorageVolume[length];
- for (int i = 0; i < length; i++) {
- result[i] = (StorageVolume)list[i];
- }
- return result;
+ return mountService.getVolumeList(userId);
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get volume list", e);
- return null;
+ throw e.rethrowAsRuntimeException();
}
}
@@ -587,9 +603,9 @@ public class StorageManager {
* Returns list of paths for all mountable volumes.
* @hide
*/
- public String[] getVolumePaths() {
+ @Deprecated
+ public @NonNull String[] getVolumePaths() {
StorageVolume[] volumes = getVolumeList();
- if (volumes == null) return null;
int count = volumes.length;
String[] paths = new String[count];
for (int i = 0; i < count; i++) {
@@ -599,21 +615,21 @@ public class StorageManager {
}
/** {@hide} */
- public StorageVolume getPrimaryVolume() {
+ public @NonNull StorageVolume getPrimaryVolume() {
return getPrimaryVolume(getVolumeList());
}
/** {@hide} */
- public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
+ public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
for (StorageVolume volume : volumes) {
if (volume.isPrimary()) {
return volume;
}
}
- Log.w(TAG, "No primary storage defined");
- return null;
+ throw new IllegalStateException("Missing primary storage");
}
+ /** {@hide} */
private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 06565f1..d66e228 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -17,81 +17,83 @@
package android.os.storage;
import android.content.Context;
+import android.net.TrafficStats;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
import java.io.CharArrayWriter;
import java.io.File;
/**
- * Description of a storage volume and its capabilities, including the
- * filesystem path where it may be mounted.
+ * Information about a storage volume that may be mounted. This is a legacy
+ * specialization of {@link VolumeInfo} which describes the volume for a
+ * specific user.
+ * <p>
+ * This class may be deprecated in the future.
*
* @hide
*/
public class StorageVolume implements Parcelable {
- // TODO: switch to more durable token
- private int mStorageId;
-
+ private final String mId;
+ private final int mStorageId;
private final File mPath;
- private final int mDescriptionId;
+ private final String mDescription;
private final boolean mPrimary;
private final boolean mRemovable;
private final boolean mEmulated;
- private final int mMtpReserveSpace;
+ private final long mMtpReserveSize;
private final boolean mAllowMassStorage;
- /** Maximum file size for the storage, or zero for no limit */
private final long mMaxFileSize;
- /** When set, indicates exclusive ownership of this volume */
private final UserHandle mOwner;
-
- private String mUuid;
- private String mUserLabel;
- private String mState;
+ private final String mFsUuid;
+ private final String mState;
// StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
// ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
// ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
- public StorageVolume(File path, int descriptionId, boolean primary, boolean removable,
- boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize,
- UserHandle owner) {
- mPath = path;
- mDescriptionId = descriptionId;
+ public StorageVolume(String id, int storageId, File path, String description, boolean primary,
+ boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
+ long maxFileSize, UserHandle owner, String fsUuid, String state) {
+ mId = Preconditions.checkNotNull(id);
+ mStorageId = storageId;
+ mPath = Preconditions.checkNotNull(path);
+ mDescription = Preconditions.checkNotNull(description);
mPrimary = primary;
mRemovable = removable;
mEmulated = emulated;
- mMtpReserveSpace = mtpReserveSpace;
+ mMtpReserveSize = mtpReserveSize;
mAllowMassStorage = allowMassStorage;
mMaxFileSize = maxFileSize;
- mOwner = owner;
+ mOwner = Preconditions.checkNotNull(owner);
+ mFsUuid = fsUuid;
+ mState = Preconditions.checkNotNull(state);
}
private StorageVolume(Parcel in) {
+ mId = in.readString();
mStorageId = in.readInt();
mPath = new File(in.readString());
- mDescriptionId = in.readInt();
+ mDescription = in.readString();
mPrimary = in.readInt() != 0;
mRemovable = in.readInt() != 0;
mEmulated = in.readInt() != 0;
- mMtpReserveSpace = in.readInt();
+ mMtpReserveSize = in.readLong();
mAllowMassStorage = in.readInt() != 0;
mMaxFileSize = in.readLong();
mOwner = in.readParcelable(null);
- mUuid = in.readString();
- mUserLabel = in.readString();
+ mFsUuid = in.readString();
mState = in.readString();
}
- public static StorageVolume fromTemplate(StorageVolume template, File path, UserHandle owner) {
- return new StorageVolume(path, template.mDescriptionId, template.mPrimary,
- template.mRemovable, template.mEmulated, template.mMtpReserveSpace,
- template.mAllowMassStorage, template.mMaxFileSize, owner);
+ public String getId() {
+ return mId;
}
/**
@@ -113,11 +115,7 @@ public class StorageVolume implements Parcelable {
* @return the volume description
*/
public String getDescription(Context context) {
- return context.getResources().getString(mDescriptionId);
- }
-
- public int getDescriptionId() {
- return mDescriptionId;
+ return mDescription;
}
public boolean isPrimary() {
@@ -153,15 +151,6 @@ public class StorageVolume implements Parcelable {
}
/**
- * Do not call this unless you are MountService
- */
- public void setStorageId(int index) {
- // storage ID is 0x00010001 for primary storage,
- // then 0x00020001, 0x00030001, etc. for secondary storages
- mStorageId = ((index + 1) << 16) + 1;
- }
-
- /**
* Number of megabytes of space to leave unallocated by MTP.
* MTP will subtract this value from the free space it reports back
* to the host via GetStorageInfo, and will not allow new files to
@@ -174,7 +163,7 @@ public class StorageVolume implements Parcelable {
* @return MTP reserve space
*/
public int getMtpReserveSpace() {
- return mMtpReserveSpace;
+ return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
}
/**
@@ -199,12 +188,8 @@ public class StorageVolume implements Parcelable {
return mOwner;
}
- public void setUuid(String uuid) {
- mUuid = uuid;
- }
-
public String getUuid() {
- return mUuid;
+ return mFsUuid;
}
/**
@@ -212,26 +197,18 @@ public class StorageVolume implements Parcelable {
* parse or UUID is unknown.
*/
public int getFatVolumeId() {
- if (mUuid == null || mUuid.length() != 9) {
+ if (mFsUuid == null || mFsUuid.length() != 9) {
return -1;
}
try {
- return (int)Long.parseLong(mUuid.replace("-", ""), 16);
+ return (int) Long.parseLong(mFsUuid.replace("-", ""), 16);
} catch (NumberFormatException e) {
return -1;
}
}
- public void setUserLabel(String userLabel) {
- mUserLabel = userLabel;
- }
-
public String getUserLabel() {
- return mUserLabel;
- }
-
- public void setState(String state) {
- mState = state;
+ return mDescription;
}
public String getState() {
@@ -262,18 +239,18 @@ public class StorageVolume implements Parcelable {
public void dump(IndentingPrintWriter pw) {
pw.println("StorageVolume:");
pw.increaseIndent();
+ pw.printPair("mId", mId);
pw.printPair("mStorageId", mStorageId);
pw.printPair("mPath", mPath);
- pw.printPair("mDescriptionId", mDescriptionId);
+ pw.printPair("mDescription", mDescription);
pw.printPair("mPrimary", mPrimary);
pw.printPair("mRemovable", mRemovable);
pw.printPair("mEmulated", mEmulated);
- pw.printPair("mMtpReserveSpace", mMtpReserveSpace);
+ pw.printPair("mMtpReserveSize", mMtpReserveSize);
pw.printPair("mAllowMassStorage", mAllowMassStorage);
pw.printPair("mMaxFileSize", mMaxFileSize);
pw.printPair("mOwner", mOwner);
- pw.printPair("mUuid", mUuid);
- pw.printPair("mUserLabel", mUserLabel);
+ pw.printPair("mFsUuid", mFsUuid);
pw.printPair("mState", mState);
pw.decreaseIndent();
}
@@ -297,18 +274,18 @@ public class StorageVolume implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mId);
parcel.writeInt(mStorageId);
parcel.writeString(mPath.toString());
- parcel.writeInt(mDescriptionId);
+ parcel.writeString(mDescription);
parcel.writeInt(mPrimary ? 1 : 0);
parcel.writeInt(mRemovable ? 1 : 0);
parcel.writeInt(mEmulated ? 1 : 0);
- parcel.writeInt(mMtpReserveSpace);
+ parcel.writeLong(mMtpReserveSize);
parcel.writeInt(mAllowMassStorage ? 1 : 0);
parcel.writeLong(mMaxFileSize);
parcel.writeParcelable(mOwner, flags);
- parcel.writeString(mUuid);
- parcel.writeString(mUserLabel);
+ parcel.writeString(mFsUuid);
parcel.writeString(mState);
}
}
diff --git a/core/java/android/os/storage/VolumeInfo.aidl b/core/java/android/os/storage/VolumeInfo.aidl
new file mode 100644
index 0000000..32d12da
--- /dev/null
+++ b/core/java/android/os/storage/VolumeInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+parcelable VolumeInfo;
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
new file mode 100644
index 0000000..2dc0361
--- /dev/null
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.storage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.mtp.MtpStorage;
+import android.os.Environment;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.DebugUtils;
+import android.util.SparseArray;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+
+import java.io.CharArrayWriter;
+import java.io.File;
+
+/**
+ * Information about a storage volume that may be mounted. A volume may be a
+ * partition on a physical {@link DiskInfo}, an emulated volume above some other
+ * storage medium, or a standalone container like an ASEC or OBB.
+ *
+ * @hide
+ */
+public class VolumeInfo implements Parcelable {
+ /** Real volume representing internal emulated storage */
+ public static final String ID_EMULATED_INTERNAL = "emulated";
+
+ public static final int TYPE_PUBLIC = 0;
+ public static final int TYPE_PRIVATE = 1;
+ public static final int TYPE_EMULATED = 2;
+ public static final int TYPE_ASEC = 3;
+ public static final int TYPE_OBB = 4;
+
+ public static final int STATE_UNMOUNTED = 0;
+ public static final int STATE_MOUNTING = 1;
+ public static final int STATE_MOUNTED = 2;
+ public static final int STATE_FORMATTING = 3;
+ public static final int STATE_UNMOUNTING = 4;
+ public static final int STATE_UNMOUNTABLE = 5;
+
+ public static final int FLAG_PRIMARY = 1 << 0;
+ public static final int FLAG_VISIBLE = 1 << 1;
+
+ private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
+ private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
+
+ static {
+ sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
+ sStateToEnvironment.put(VolumeInfo.STATE_MOUNTING, Environment.MEDIA_CHECKING);
+ sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
+ sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
+ sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTING, Environment.MEDIA_EJECTING);
+ sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
+
+ sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
+ sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
+ sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
+ sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
+ sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE);
+ }
+
+ /** vold state */
+ public final String id;
+ public final int type;
+ public int flags = 0;
+ public int userId = -1;
+ public int state = STATE_UNMOUNTED;
+ public String fsType;
+ public String fsUuid;
+ public String fsLabel;
+ public String path;
+
+ /** Framework state */
+ public final int mtpIndex;
+ public String nickname;
+
+ public DiskInfo disk;
+
+ public VolumeInfo(String id, int type, int mtpIndex) {
+ this.id = Preconditions.checkNotNull(id);
+ this.type = type;
+ this.mtpIndex = mtpIndex;
+ }
+
+ public VolumeInfo(Parcel parcel) {
+ id = parcel.readString();
+ type = parcel.readInt();
+ flags = parcel.readInt();
+ userId = parcel.readInt();
+ state = parcel.readInt();
+ fsType = parcel.readString();
+ fsUuid = parcel.readString();
+ fsLabel = parcel.readString();
+ path = parcel.readString();
+ mtpIndex = parcel.readInt();
+ nickname = parcel.readString();
+ }
+
+ public static @NonNull String getEnvironmentForState(int state) {
+ final String envState = sStateToEnvironment.get(state);
+ if (envState != null) {
+ return envState;
+ } else {
+ return Environment.MEDIA_UNKNOWN;
+ }
+ }
+
+ public static @Nullable String getBroadcastForEnvironment(String envState) {
+ return sEnvironmentToBroadcast.get(envState);
+ }
+
+ public static @Nullable String getBroadcastForState(int state) {
+ return getBroadcastForEnvironment(getEnvironmentForState(state));
+ }
+
+ public String getDescription(Context context) {
+ if (ID_EMULATED_INTERNAL.equals(id)) {
+ return context.getString(com.android.internal.R.string.storage_internal);
+ } else if (!TextUtils.isEmpty(nickname)) {
+ return nickname;
+ } else if (!TextUtils.isEmpty(fsLabel)) {
+ return fsLabel;
+ } else {
+ return null;
+ }
+ }
+
+ public boolean isPrimary() {
+ return (flags & FLAG_PRIMARY) != 0;
+ }
+
+ public boolean isVisible() {
+ return (flags & FLAG_VISIBLE) != 0;
+ }
+
+ public boolean isVisibleToUser(int userId) {
+ if (type == TYPE_PUBLIC && userId == this.userId) {
+ return isVisible();
+ } else if (type == TYPE_EMULATED) {
+ return isVisible();
+ } else {
+ return false;
+ }
+ }
+
+ public File getPathForUser(int userId) {
+ if (path == null) {
+ return null;
+ } else if (type == TYPE_PUBLIC && userId == this.userId) {
+ return new File(path);
+ } else if (type == TYPE_EMULATED) {
+ return new File(path, Integer.toString(userId));
+ } else {
+ return null;
+ }
+ }
+
+ public StorageVolume buildStorageVolume(Context context, int userId) {
+ final boolean removable;
+ final boolean emulated;
+ final boolean allowMassStorage = false;
+ final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex);
+ final String envState = getEnvironmentForState(state);
+
+ File userPath = getPathForUser(userId);
+ if (userPath == null) {
+ userPath = new File("/dev/null");
+ }
+
+ String description = getDescription(context);
+ if (description == null) {
+ description = context.getString(android.R.string.unknownName);
+ }
+
+ long mtpReserveSize = 0;
+ long maxFileSize = 0;
+
+ if (type == TYPE_EMULATED) {
+ emulated = true;
+ mtpReserveSize = StorageManager.from(context).getStorageLowBytes(userPath);
+
+ if (ID_EMULATED_INTERNAL.equals(id)) {
+ removable = false;
+ } else {
+ removable = true;
+ }
+
+ } else if (type == TYPE_PUBLIC) {
+ emulated = false;
+ removable = true;
+
+ if ("vfat".equals(fsType)) {
+ maxFileSize = 4294967295L;
+ }
+
+ } else {
+ throw new IllegalStateException("Unexpected volume type " + type);
+ }
+
+ return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable,
+ emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId),
+ fsUuid, envState);
+ }
+
+ @Override
+ public String toString() {
+ final CharArrayWriter writer = new CharArrayWriter();
+ dump(new IndentingPrintWriter(writer, " ", 80));
+ return writer.toString();
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("VolumeInfo:");
+ pw.increaseIndent();
+ pw.printPair("id", id);
+ pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
+ pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
+ pw.printPair("userId", userId);
+ pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
+ pw.println();
+ pw.printPair("fsType", fsType);
+ pw.printPair("fsUuid", fsUuid);
+ pw.printPair("fsLabel", fsLabel);
+ pw.println();
+ pw.printPair("path", path);
+ pw.printPair("mtpIndex", mtpIndex);
+ pw.decreaseIndent();
+ pw.println();
+ }
+
+ @Override
+ public VolumeInfo clone() {
+ final Parcel temp = Parcel.obtain();
+ try {
+ writeToParcel(temp, 0);
+ temp.setDataPosition(0);
+ return CREATOR.createFromParcel(temp);
+ } finally {
+ temp.recycle();
+ }
+ }
+
+ public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() {
+ @Override
+ public VolumeInfo createFromParcel(Parcel in) {
+ return new VolumeInfo(in);
+ }
+
+ @Override
+ public VolumeInfo[] newArray(int size) {
+ return new VolumeInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(id);
+ parcel.writeInt(type);
+ parcel.writeInt(flags);
+ parcel.writeInt(userId);
+ parcel.writeInt(state);
+ parcel.writeString(fsType);
+ parcel.writeString(fsUuid);
+ parcel.writeString(fsLabel);
+ parcel.writeString(path);
+ parcel.writeInt(mtpIndex);
+ parcel.writeString(nickname);
+ }
+}
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index f32e8cf..3b482eb 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -1438,7 +1438,7 @@ public class Preference implements Comparable<Preference> {
protected boolean persistString(String value) {
if (shouldPersist()) {
// Shouldn't store null
- if (value == getPersistedString(null)) {
+ if (TextUtils.equals(value, getPersistedString(null))) {
// It's already there, so the same as persisting
return true;
}
diff --git a/core/java/android/provider/AlarmClock.java b/core/java/android/provider/AlarmClock.java
index 724d76d..25a35e1 100644
--- a/core/java/android/provider/AlarmClock.java
+++ b/core/java/android/provider/AlarmClock.java
@@ -43,8 +43,14 @@ public final class AlarmClock {
* should remove this alarm after it has been dismissed. If an identical alarm exists matching
* all parameters, the implementation may re-use it instead of creating a new one (in this case,
* the alarm should not be removed after dismissal).
- *
+ * </p><p>
* This action always enables the alarm.
+ * </p><p>
+ * This activity could also be started in Voice Interaction mode. The activity should check
+ * {@link android.app.Activity#isVoiceInteraction}, and if true, the implementation should
+ * report a deeplink of the created/enabled alarm using
+ * {@link android.app.VoiceInteractor.CompleteVoiceRequest}. This allows follow-on voice actions
+ * such as {@link #ACTION_VOICE_CANCEL_ALARM} to cancel the alarm that was just enabled.
* </p>
* <h3>Request parameters</h3>
* <ul>
@@ -63,6 +69,48 @@ public final class AlarmClock {
public static final String ACTION_SET_ALARM = "android.intent.action.SET_ALARM";
/**
+ * Voice Activity Action: Cancel an alarm.
+ * Requires: The activity must check {@link android.app.Activity#isVoiceInteraction}, i.e. be
+ * started in Voice Interaction mode.
+ * <p>
+ * Cancels the specified alarm by voice. To cancel means to disable, but not delete, the alarm.
+ * See {@link #ACTION_VOICE_DELETE_ALARM} to delete an alarm by voice.
+ * </p><p>
+ * The alarm to cancel can be specified or searched for in one of the following ways:
+ * <ol>
+ * <li>The Intent's data URI specifies a deeplink to the alarm.
+ * <li>If the Intent's data URI is unspecified, then the extra {@link #EXTRA_ALARM_SEARCH_MODE} is
+ * required to determine how to search for the alarm.
+ *
+ * @see #ACTION_VOICE_DELETE_ALARM
+ * @see #EXTRA_ALARM_SEARCH_MODE
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VOICE_CANCEL_ALARM =
+ "android.intent.action.VOICE_CANCEL_ALARM";
+
+ /**
+ * Voice Activity Action: Delete an alarm.
+ * Requires: The activity must check {@link android.app.Activity#isVoiceInteraction}, i.e. be
+ * started in Voice Interaction mode.
+ * <p>
+ * Deletes the specified alarm by voice.
+ * See {@link #ACTION_VOICE_CANCEL_ALARM} to cancel (disable) an alarm by voice.
+ * </p><p>
+ * The alarm to delete can be specified or searched for in one of the following ways:
+ * <ol>
+ * <li>The Intent's data URI specifies a deeplink to the alarm.
+ * <li>If the Intent's data URI is unspecified, then the extra {@link #EXTRA_ALARM_SEARCH_MODE} is
+ * required to determine how to search for the alarm.
+ *
+ * @see #ACTION_VOICE_CANCEL_ALARM
+ * @see #EXTRA_ALARM_SEARCH_MODE
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VOICE_DELETE_ALARM =
+ "android.intent.action.VOICE_DELETE_ALARM";
+
+ /**
* Activity Action: Set a timer.
* <p>
* Activates an existing timer or creates a new one.
@@ -99,6 +147,100 @@ public final class AlarmClock {
public static final String ACTION_SHOW_ALARMS = "android.intent.action.SHOW_ALARMS";
/**
+ * Bundle extra: Specify the type of search mode to look up an alarm.
+ * <p>
+ * Used by {@link #ACTION_VOICE_CANCEL_ALARM} and {@link #ACTION_VOICE_DELETE_ALARM} to identify
+ * the alarm(s) to cancel or delete, respectively.
+ * </p><p>
+ * This extra is only required when the alarm is not already identified by a deeplink as
+ * specified in the Intent's data URI.
+ * </p><p>
+ * The value of this extra is a {@link String}, restricted to the following set of supported
+ * search modes:
+ * <ul>
+ * <li><i>Time</i> - {@link #ALARM_SEARCH_MODE_TIME}: Selects the alarm that is most
+ * closely matched by the search parameters {@link #EXTRA_HOUR}, {@link #EXTRA_MINUTES},
+ * {@link #EXTRA_IS_PM}.
+ * <li><i>Next alarm</i> - {@link #ALARM_SEARCH_MODE_NEXT}: Selects the alarm that will
+ * ring next, or the alarm that is currently ringing, if any.
+ * <li><i>All alarms</i> - {@link #ALARM_SEARCH_MODE_ALL}: Selects all alarms.
+ * <li><i>None</i> - {@link #ALARM_SEARCH_MODE_NONE}: No search mode specified. The
+ * implementation should ask the user to select a search mode using
+ * {@link android.app.VoiceInteractor.PickOptionRequest} and proceed with a voice flow to
+ * identify the alarm.
+ * </ul>
+ * </ol>
+ *
+ * @see #ALARM_SEARCH_MODE_TIME
+ * @see #ALARM_SEARCH_MODE_NEXT
+ * @see #ALARM_SEARCH_MODE_ALL
+ * @see #ALARM_SEARCH_MODE_NONE
+ * @see #ACTION_VOICE_CANCEL_ALARM
+ * @see #ACTION_VOICE_DELETE_ALARM
+ */
+ public static final String EXTRA_ALARM_SEARCH_MODE =
+ "android.intent.extra.alarm.ALARM_SEARCH_MODE";
+
+ /**
+ * Search for the alarm that is most closely matched by the search parameters
+ * {@link #EXTRA_HOUR}, {@link #EXTRA_MINUTES}, {@link #EXTRA_IS_PM}.
+ * In this search mode, at least one of these additional extras are required.
+ * <ul>
+ * <li>{@link #EXTRA_HOUR} - The hour to search for the alarm.
+ * <li>{@link #EXTRA_MINUTES} - The minute to search for the alarm.
+ * <li>{@link #EXTRA_IS_PM} - Whether the hour is AM or PM.
+ * </ul>
+ *
+ * @see #EXTRA_ALARM_SEARCH_MODE
+ */
+ public static final String ALARM_SEARCH_MODE_TIME = "time";
+
+ /**
+ * Selects the alarm that will ring next, or the alarm that is currently ringing, if any.
+ *
+ * @see #EXTRA_ALARM_SEARCH_MODE
+ */
+ public static final String ALARM_SEARCH_MODE_NEXT = "next";
+
+ /**
+ * Selects all alarms.
+ *
+ * @see #EXTRA_ALARM_SEARCH_MODE
+ */
+ public static final String ALARM_SEARCH_MODE_ALL = "all";
+
+ /**
+ * No search mode specified. The implementation should ask the user to select a search mode
+ * using {@link android.app.VoiceInteractor.PickOptionRequest} and proceed with a voice flow to
+ * identify the alarm.
+ *
+ * @see #EXTRA_ALARM_SEARCH_MODE
+ */
+ public static final String ALARM_SEARCH_MODE_NONE = "none";
+
+ /**
+ * Bundle extra: The AM/PM of the alarm.
+ * <p>
+ * Used by {@link #ACTION_VOICE_CANCEL_ALARM} and {@link #ACTION_VOICE_DELETE_ALARM}.
+ * </p><p>
+ * This extra is optional and only used when {@link #EXTRA_ALARM_SEARCH_MODE} is set to
+ * {@link #ALARM_SEARCH_MODE_TIME}. In this search mode, the {@link #EXTRA_IS_PM} is
+ * used together with {@link #EXTRA_HOUR} and {@link #EXTRA_MINUTES}. The implementation should
+ * look up the alarm that is most closely matched by these search parameters.
+ * If {@link #EXTRA_IS_PM} is missing, then the AM/PM of the specified {@link #EXTRA_HOUR} is
+ * ambiguous and the implementation should ask for clarification from the user.
+ * </p><p>
+ * The value is a {@link Boolean}, where false=AM and true=PM.
+ * </p>
+ *
+ * @see #ACTION_VOICE_CANCEL_ALARM
+ * @see #ACTION_VOICE_DELETE_ALARM
+ * @see #EXTRA_HOUR
+ * @see #EXTRA_MINUTES
+ */
+ public static final String EXTRA_IS_PM = "android.intent.extra.alarm.IS_PM";
+
+ /**
* Bundle extra: Weekdays for repeating alarm.
* <p>
* Used by {@link #ACTION_SET_ALARM}.
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 6517f35..7d57233 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -454,7 +454,6 @@ public class CallLog {
long start, int duration, Long dataUsage, boolean addForAllUsers) {
final ContentResolver resolver = context.getContentResolver();
int numberPresentation = PRESENTATION_ALLOWED;
- boolean isHidden = false;
TelecomManager tm = null;
try {
@@ -469,12 +468,6 @@ public class CallLog {
if (address != null) {
accountAddress = address.getSchemeSpecificPart();
}
- } else {
- // We could not find the account through telecom. For call log entries that
- // are added with a phone account which is not registered, we automatically
- // mark them as hidden. They are unhidden once the account is registered.
- Log.i(LOG_TAG, "Marking call log entry as hidden.");
- isHidden = true;
}
}
@@ -520,7 +513,6 @@ public class CallLog {
values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString);
values.put(PHONE_ACCOUNT_ID, accountId);
values.put(PHONE_ACCOUNT_ADDRESS, accountAddress);
- values.put(PHONE_ACCOUNT_HIDDEN, Integer.valueOf(isHidden ? 1 : 0));
values.put(NEW, Integer.valueOf(1));
if (callType == MISSED_TYPE) {
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 74b0a1c..bf7f3cb 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -18,6 +18,7 @@ package android.provider;
import android.accounts.Account;
import android.app.Activity;
+import android.app.admin.DevicePolicyManager;
import android.content.ActivityNotFoundException;
import android.content.ContentProviderClient;
import android.content.ContentProviderOperation;
@@ -1628,7 +1629,6 @@ public final class ContactsContract {
*/
public static final String CONTENT_VCARD_TYPE = "text/x-vcard";
-
/**
* Mimimal ID for corp contacts returned from
* {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}.
@@ -1638,6 +1638,14 @@ public final class ContactsContract {
public static long ENTERPRISE_CONTACT_ID_BASE = 1000000000; // slightly smaller than 2 ** 30
/**
+ * Prefix for corp contacts returned from
+ * {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI}.
+ *
+ * @hide
+ */
+ public static String ENTERPRISE_CONTACT_LOOKUP_PREFIX = "c-";
+
+ /**
* Return TRUE if a contact ID is from the contacts provider on the enterprise profile.
*
* {@link PhoneLookup#ENTERPRISE_CONTENT_FILTER_URI} may return such a contact.
@@ -4809,6 +4817,14 @@ public final class ContactsContract {
Uri.withAppendedPath(AUTHORITY_URI, "raw_contact_entities");
/**
+ * The content:// style URI for this table in corp profile
+ *
+ * @hide
+ */
+ public static final Uri CORP_CONTENT_URI =
+ Uri.withAppendedPath(AUTHORITY_URI, "raw_contact_entities_corp");
+
+ /**
* The content:// style URI for this table, specific to the user's profile.
*/
public static final Uri PROFILE_CONTENT_URI =
@@ -5024,9 +5040,17 @@ public final class ContactsContract {
* is from the corp profile, use
* {@link ContactsContract.Contacts#isEnterpriseContactId(long)}.
* </li>
+ * <li>
+ * Corp contacts will get artificial {@link #LOOKUP_KEY}s too.
+ * </li>
* </ul>
* <p>
- * This URI does NOT support selection nor order-by.
+ * A contact lookup URL built by
+ * {@link ContactsContract.Contacts#getLookupUri(long, String)}
+ * with an {@link #_ID} and a {@link #LOOKUP_KEY} returned by this API can be passed to
+ * {@link ContactsContract.QuickContact#showQuickContact} even if a contact is from the
+ * corp profile.
+ * </p>
*
* <pre>
* Uri lookupUri = Uri.withAppendedPath(PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI,
@@ -6017,10 +6041,18 @@ public final class ContactsContract {
* a contact
* is from the corp profile, use
* {@link ContactsContract.Contacts#isEnterpriseContactId(long)}.
- * </li>
- * </ul>
- * <p>
- * This URI does NOT support selection nor order-by.
+ * </li>
+ * <li>
+ * Corp contacts will get artificial {@link #LOOKUP_KEY}s too.
+ * </li>
+ * </ul>
+ * <p>
+ * A contact lookup URL built by
+ * {@link ContactsContract.Contacts#getLookupUri(long, String)}
+ * with an {@link #_ID} and a {@link #LOOKUP_KEY} returned by this API can be passed to
+ * {@link ContactsContract.QuickContact#showQuickContact} even if a contact is from the
+ * corp profile.
+ * </p>
*
* <pre>
* Uri lookupUri = Uri.withAppendedPath(Email.ENTERPRISE_CONTENT_LOOKUP_URI,
@@ -8174,6 +8206,9 @@ public final class ContactsContract {
*/
public static final int MODE_LARGE = 3;
+ /** @hide */
+ public static final int MODE_DEFAULT = MODE_LARGE;
+
/**
* Constructs the QuickContacts intent with a view's rect.
* @hide
@@ -8216,6 +8251,7 @@ public final class ContactsContract {
// Launch pivot dialog through intent for now
final Intent intent = new Intent(ACTION_QUICK_CONTACT).addFlags(intentFlags);
+ // NOTE: This logic and rebuildManagedQuickContactsIntent() must be in sync.
intent.setData(lookupUri);
intent.setSourceBounds(target);
intent.putExtra(EXTRA_MODE, mode);
@@ -8224,6 +8260,30 @@ public final class ContactsContract {
}
/**
+ * Constructs a QuickContacts intent based on an incoming intent for DevicePolicyManager
+ * to strip off anything not necessary.
+ *
+ * @hide
+ */
+ public static Intent rebuildManagedQuickContactsIntent(String lookupKey, long contactId,
+ Intent originalIntent) {
+ final Intent intent = new Intent(ACTION_QUICK_CONTACT);
+ // Rebuild the URI from a lookup key and a contact ID.
+ intent.setData(Contacts.getLookupUri(contactId, lookupKey));
+
+ // Copy flags and always set NEW_TASK because it won't have a parent activity.
+ intent.setFlags(originalIntent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ // Copy extras.
+ intent.setSourceBounds(originalIntent.getSourceBounds());
+ intent.putExtra(EXTRA_MODE, originalIntent.getIntExtra(EXTRA_MODE, MODE_DEFAULT));
+ intent.putExtra(EXTRA_EXCLUDE_MIMES,
+ originalIntent.getStringArrayExtra(EXTRA_EXCLUDE_MIMES));
+ return intent;
+ }
+
+
+ /**
* Trigger a dialog that lists the various methods of interacting with
* the requested {@link Contacts} entry. This may be based on available
* {@link ContactsContract.Data} rows under that contact, and may also
@@ -8251,7 +8311,7 @@ public final class ContactsContract {
// Trigger with obtained rectangle
Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
excludeMimes);
- startActivityWithErrorToast(context, intent);
+ ContactsInternal.startQuickContactWithErrorToast(context, intent);
}
/**
@@ -8284,7 +8344,7 @@ public final class ContactsContract {
String[] excludeMimes) {
Intent intent = composeQuickContactsIntent(context, target, lookupUri, mode,
excludeMimes);
- startActivityWithErrorToast(context, intent);
+ ContactsInternal.startQuickContactWithErrorToast(context, intent);
}
/**
@@ -8317,10 +8377,10 @@ public final class ContactsContract {
// Use MODE_LARGE instead of accepting mode as a parameter. The different mode
// values defined in ContactsContract only affect very old implementations
// of QuickContacts.
- Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_LARGE,
+ Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_DEFAULT,
excludeMimes);
intent.putExtra(EXTRA_PRIORITIZED_MIMETYPE, prioritizedMimeType);
- startActivityWithErrorToast(context, intent);
+ ContactsInternal.startQuickContactWithErrorToast(context, intent);
}
/**
@@ -8355,19 +8415,10 @@ public final class ContactsContract {
// Use MODE_LARGE instead of accepting mode as a parameter. The different mode
// values defined in ContactsContract only affect very old implementations
// of QuickContacts.
- Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_LARGE,
+ Intent intent = composeQuickContactsIntent(context, target, lookupUri, MODE_DEFAULT,
excludeMimes);
intent.putExtra(EXTRA_PRIORITIZED_MIMETYPE, prioritizedMimeType);
- startActivityWithErrorToast(context, intent);
- }
-
- private static void startActivityWithErrorToast(Context context, Intent intent) {
- try {
- context.startActivity(intent);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(context, com.android.internal.R.string.quick_contacts_not_available,
- Toast.LENGTH_SHORT).show();
- }
+ ContactsInternal.startQuickContactWithErrorToast(context, intent);
}
}
diff --git a/core/java/android/provider/ContactsInternal.java b/core/java/android/provider/ContactsInternal.java
new file mode 100644
index 0000000..059a603
--- /dev/null
+++ b/core/java/android/provider/ContactsInternal.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package android.provider;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ActivityNotFoundException;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import java.util.List;
+
+/**
+ * Contacts related internal methods.
+ *
+ * @hide
+ */
+public class ContactsInternal {
+ private ContactsInternal() {
+ }
+
+ /** URI matcher used to parse contact URIs. */
+ private static final UriMatcher sContactsUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+
+ private static final int CONTACTS_URI_LOOKUP_ID = 1000;
+
+ static {
+ // Contacts URI matching table
+ final UriMatcher matcher = sContactsUriMatcher;
+ matcher.addURI(ContactsContract.AUTHORITY, "contacts/lookup/*/#", CONTACTS_URI_LOOKUP_ID);
+ }
+
+ /**
+ * Called by {@link ContactsContract} to star Quick Contact, possibly on the managed profile.
+ */
+ public static void startQuickContactWithErrorToast(Context context, Intent intent) {
+ final Uri uri = intent.getData();
+
+ final int match = sContactsUriMatcher.match(uri);
+ switch (match) {
+ case CONTACTS_URI_LOOKUP_ID: {
+ if (maybeStartManagedQuickContact(context, intent)) {
+ return; // Request handled by DPM. Just return here.
+ }
+ break;
+ }
+ }
+ // Launch on the current profile.
+ startQuickContactWithErrorToastForUser(context, intent, Process.myUserHandle());
+ }
+
+ public static void startQuickContactWithErrorToastForUser(Context context, Intent intent,
+ UserHandle user) {
+ try {
+ context.startActivityAsUser(intent, user);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(context, com.android.internal.R.string.quick_contacts_not_available,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /**
+ * If the URI in {@code intent} is of a corp contact, launch quick contact on the managed
+ * profile.
+ *
+ * @return the URI in {@code intent} is of a corp contact thus launched on the managed profile.
+ */
+ private static boolean maybeStartManagedQuickContact(Context context, Intent originalIntent) {
+ final Uri uri = originalIntent.getData();
+
+ // Decompose into an ID and a lookup key.
+ final List<String> pathSegments = uri.getPathSegments();
+ final long contactId = ContentUris.parseId(uri);
+ final String lookupKey = pathSegments.get(2);
+
+ // See if it has a corp lookupkey.
+ if (TextUtils.isEmpty(lookupKey)
+ || !lookupKey.startsWith(
+ ContactsContract.Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX)) {
+ return false; // It's not a corp lookup key.
+ }
+
+ // Launch Quick Contact on the managed profile, if the policy allows.
+ final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
+ final String actualLookupKey = lookupKey.substring(
+ ContactsContract.Contacts.ENTERPRISE_CONTACT_LOOKUP_PREFIX.length());
+ final long actualContactId =
+ (contactId - ContactsContract.Contacts.ENTERPRISE_CONTACT_ID_BASE);
+
+ dpm.startManagedQuickContact(actualLookupKey, actualContactId, originalIntent);
+ return true;
+ }
+}
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 5afbd6d..7565654b 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -226,6 +226,35 @@ public final class MediaStore {
public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA";
/**
+ * The name of the Intent action used to indicate that a camera launch might be imminent. This
+ * broadcast should be targeted to the package that is receiving
+ * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA} or
+ * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE}, depending on the context. If such
+ * intent would launch the resolver activity, this broadcast should not be sent at all.
+ * <p>
+ * A receiver of this broadcast should do the absolute minimum amount of work to initialize the
+ * camera in order to reduce startup time in likely case that shortly after an actual camera
+ * launch intent would be sent.
+ * <p>
+ * In case the actual intent will not be fired, the target package will receive
+ * {@link #ACTION_STILL_IMAGE_CAMERA_COOLDOWN}. However, it is recommended that the receiver
+ * also implements a timeout to close the camera after receiving this intent, as there is no
+ * guarantee that {@link #ACTION_STILL_IMAGE_CAMERA_COOLDOWN} will be delivered.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_STILL_IMAGE_CAMERA_PREWARM = "android.media.action.STILL_IMAGE_CAMERA_PREWARM";
+
+ /**
+ * The name of the Intent action used to indicate that an imminent camera launch has been
+ * cancelled by the user. This broadcast should be targeted to the package that has received
+ * {@link #ACTION_STILL_IMAGE_CAMERA_PREWARM}.
+ * <p>
+ * A receiver of this broadcast should close the camera immediately.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_STILL_IMAGE_CAMERA_COOLDOWN = "android.media.action.STILL_IMAGE_CAMERA_COOLDOWN";
+
+ /**
* The name of the Intent action used to launch a camera in still image mode
* for use when the device is secured (e.g. with a pin, password, pattern,
* or face unlock). Applications responding to this intent must not expose
diff --git a/core/java/android/provider/SearchIndexableData.java b/core/java/android/provider/SearchIndexableData.java
index 7b9d1ea..5e0a76d 100644
--- a/core/java/android/provider/SearchIndexableData.java
+++ b/core/java/android/provider/SearchIndexableData.java
@@ -16,6 +16,7 @@
package android.provider;
+import android.annotation.SystemApi;
import android.content.Context;
import java.util.Locale;
@@ -27,6 +28,7 @@ import java.util.Locale;
*
* @hide
*/
+@SystemApi
public abstract class SearchIndexableData {
/**
diff --git a/core/java/android/provider/SearchIndexableResource.java b/core/java/android/provider/SearchIndexableResource.java
index c807df2..1eb1734 100644
--- a/core/java/android/provider/SearchIndexableResource.java
+++ b/core/java/android/provider/SearchIndexableResource.java
@@ -16,6 +16,7 @@
package android.provider;
+import android.annotation.SystemApi;
import android.content.Context;
/**
@@ -31,6 +32,7 @@ import android.content.Context;
*
* @hide
*/
+@SystemApi
public class SearchIndexableResource extends SearchIndexableData {
/**
diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java
index 1b5f72a..93ac7f6 100644
--- a/core/java/android/provider/SearchIndexablesContract.java
+++ b/core/java/android/provider/SearchIndexablesContract.java
@@ -16,6 +16,7 @@
package android.provider;
+import android.annotation.SystemApi;
import android.content.ContentResolver;
/**
@@ -23,6 +24,7 @@ import android.content.ContentResolver;
*
* @hide
*/
+@SystemApi
public class SearchIndexablesContract {
/**
@@ -234,7 +236,7 @@ public class SearchIndexablesContract {
/**
* The base columns.
*/
- private static class BaseColumns {
+ public static class BaseColumns {
private BaseColumns() {
}
diff --git a/core/java/android/provider/SearchIndexablesProvider.java b/core/java/android/provider/SearchIndexablesProvider.java
index 9c8f6d0..3120e54 100644
--- a/core/java/android/provider/SearchIndexablesProvider.java
+++ b/core/java/android/provider/SearchIndexablesProvider.java
@@ -16,6 +16,7 @@
package android.provider;
+import android.annotation.SystemApi;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
@@ -61,6 +62,7 @@ import android.net.Uri;
*
* @hide
*/
+@SystemApi
public abstract class SearchIndexablesProvider extends ContentProvider {
private static final String TAG = "IndexablesProvider";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index de536bd..5ee8fb3 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -138,7 +138,7 @@ public final class Settings {
"android.settings.AIRPLANE_MODE_SETTINGS";
/**
- * Activity Action: Modify Airplane mode settings using the users voice.
+ * Activity Action: Modify Airplane mode settings using a voice command.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard against this.
* <p>
@@ -146,15 +146,13 @@ public final class Settings {
* {@link android.service.voice.VoiceInteractionSession#startVoiceActivity
* startVoiceActivity}.
* <p>
- * To tell which state airplane mode should be set to, add the
- * {@link #EXTRA_AIRPLANE_MODE_ENABLED} extra to this Intent with the state specified.
- * If there is no extra in this Intent, no changes will be made.
- * <p>
- * The activity should verify that
+ * Note: The activity implementing this intent MUST verify that
* {@link android.app.Activity#isVoiceInteraction isVoiceInteraction} returns true before
* modifying the setting.
* <p>
- * Input: Nothing.
+ * Input: To tell which state airplane mode should be set to, add the
+ * {@link #EXTRA_AIRPLANE_MODE_ENABLED} extra to this Intent with the state specified.
+ * If the extra is not included, no changes will be made.
* <p>
* Output: Nothing.
*/
@@ -839,6 +837,49 @@ public final class Settings {
public static final String ACTION_ZEN_MODE_SETTINGS = "android.settings.ZEN_MODE_SETTINGS";
/**
+ * Activity Action: Show Zen Mode priority configuration settings.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ZEN_MODE_PRIORITY_SETTINGS
+ = "android.settings.ZEN_MODE_PRIORITY_SETTINGS";
+
+ /**
+ * Activity Action: Show Zen Mode automation configuration settings.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_ZEN_MODE_AUTOMATION_SETTINGS
+ = "android.settings.ZEN_MODE_AUTOMATION_SETTINGS";
+
+ /**
+ * Activity Action: Modify do not disturb mode settings.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+ * <p>
+ * This intent MUST be started using
+ * {@link android.service.voice.VoiceInteractionSession#startVoiceActivity
+ * startVoiceActivity}.
+ * <p>
+ * Note: The Activity implementing this intent MUST verify that
+ * {@link android.app.Activity#isVoiceInteraction isVoiceInteraction}.
+ * returns true before modifying the setting.
+ * <p>
+ * Input: The optional {@link #EXTRA_DO_NOT_DISTURB_MODE_MINUTES} extra can be used to indicate
+ * how long the user wishes to avoid interruptions for. The optional
+ * {@link #EXTRA_DO_NOT_DISTURB_MODE_ENABLED} extra can be to indicate if the user is
+ * enabling or disabling do not disturb mode. If either extra is not included, the
+ * user maybe asked to provide the value.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE =
+ "android.settings.VOICE_CONTROL_DO_NOT_DISTURB_MODE";
+
+ /**
* Activity Action: Show the regulatory information screen for the device.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard
@@ -885,6 +926,29 @@ public final class Settings {
= "android.settings.BATTERY_SAVER_SETTINGS";
/**
+ * Activity Action: Modify Battery Saver mode setting using a voice command.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+ * <p>
+ * This intent MUST be started using
+ * {@link android.service.voice.VoiceInteractionSession#startVoiceActivity
+ * startVoiceActivity}.
+ * <p>
+ * Note: The activity implementing this intent MUST verify that
+ * {@link android.app.Activity#isVoiceInteraction isVoiceInteraction} returns true before
+ * modifying the setting.
+ * <p>
+ * Input: To tell which state batter saver mode should be set to, add the
+ * {@link #EXTRA_BATTERY_SAVER_MODE_ENABLED} extra to this Intent with the state specified.
+ * If the extra is not included, no changes will be made.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE =
+ "android.settings.VOICE_CONTROL_BATTERY_SAVER_MODE";
+
+ /**
* Activity Action: Show Home selection settings. If there are multiple activities
* that can satisfy the {@link Intent#CATEGORY_HOME} intent, this screen allows you
* to pick your preferred activity.
@@ -998,10 +1062,37 @@ public final class Settings {
* Activity Extra: Enable or disable Airplane Mode.
* <p>
* This can be passed as an extra field to the {@link #ACTION_VOICE_CONTROL_AIRPLANE_MODE}
- * intent as a boolean.
+ * intent as a boolean to indicate if it should be enabled.
*/
public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
+ /**
+ * Activity Extra: Enable or disable Battery saver mode.
+ * <p>
+ * This can be passed as an extra field to the {@link #ACTION_VOICE_CONTROL_BATTERY_SAVER_MODE}
+ * intent as a boolean to indicate if it should be enabled.
+ */
+ public static final String EXTRA_BATTERY_SAVER_MODE_ENABLED =
+ "android.settings.extra.battery_saver_mode_enabled";
+
+ /**
+ * Activity Extra: Enable or disable Do Not Disturb mode.
+ * <p>
+ * This can be passed as an extra field to the {@link #ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE}
+ * intent as a boolean to indicate if it should be enabled.
+ */
+ public static final String EXTRA_DO_NOT_DISTURB_MODE_ENABLED =
+ "android.settings.extra.do_not_disturb_mode_enabled";
+
+ /**
+ * Activity Extra: How many minutes to enable do not disturb mode for.
+ * <p>
+ * This can be passed as an extra field to the {@link #ACTION_VOICE_CONTROL_DO_NOT_DISTURB_MODE}
+ * intent to indicate how long do not disturb mode should be enabled for.
+ */
+ public static final String EXTRA_DO_NOT_DISTURB_MODE_MINUTES =
+ "android.settings.extra.do_not_disturb_mode_minutes";
+
private static final String JID_RESOURCE_PREFIX = "android";
public static final String AUTHORITY = "settings";
@@ -3026,7 +3117,7 @@ public final class Settings {
};
/**
- * These are all pulbic system settings
+ * These are all public system settings
*
* @hide
*/
@@ -3126,7 +3217,7 @@ public final class Settings {
}
/**
- * These are all pulbic system settings
+ * These are all public system settings
*
* @hide
*/
@@ -5255,6 +5346,15 @@ public final class Settings {
public static final String SMS_DEFAULT_APPLICATION = "sms_default_application";
/**
+ * Specifies the package name currently configured to be the emergency assistance application
+ *
+ * @see android.telephony.TelephonyManager#ACTION_EMERGENCY_ASSISTANCE
+ *
+ * @hide
+ */
+ public static final String EMERGENCY_ASSISTANCE_APPLICATION = "emergency_assistance_application";
+
+ /**
* Names of the packages that the current user has explicitly allowed to
* see all of the user's notifications, separated by ':'.
*
@@ -5354,6 +5454,13 @@ public final class Settings {
public static final String SLEEP_TIMEOUT = "sleep_timeout";
/**
+ * Duration in milliseconds that an app should be inactive before it is considered idle.
+ * <p/>Type: Long
+ * @hide
+ */
+ public static final String APP_IDLE_DURATION = "app_idle_duration";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -5416,6 +5523,7 @@ public final class Settings {
* since the managed profile doesn't get to change them.
*/
private static final Set<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+
static {
CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_ENABLED);
CLONE_TO_MANAGED_PROFILE.add(ALLOW_MOCK_LOCATION);
@@ -6088,7 +6196,7 @@ public final class Settings {
public static final String PACKAGE_VERIFIER_SETTING_VISIBLE = "verifier_setting_visible";
/**
- * Run package verificaiton on apps installed through ADB/ADT/USB
+ * Run package verification on apps installed through ADB/ADT/USB
* 1 = perform package verification on ADB installs (default)
* 0 = bypass package verification on ADB installs
* @hide
@@ -6366,6 +6474,14 @@ public final class Settings {
"wifi_scan_always_enabled";
/**
+ * Settings to allow BLE scans to be enabled even when Bluetooth is turned off for
+ * connectivity.
+ * @hide
+ */
+ public static final String BLE_SCAN_ALWAYS_AVAILABLE =
+ "ble_scan_always_enabled";
+
+ /**
* Used to save the Wifi_ON state prior to tethering.
* This state will be checked to restore Wifi after
* the user turns off tethering.
@@ -7093,9 +7209,11 @@ public final class Settings {
/** @hide */ public static final int ZEN_MODE_OFF = 0;
/** @hide */ public static final int ZEN_MODE_IMPORTANT_INTERRUPTIONS = 1;
/** @hide */ public static final int ZEN_MODE_NO_INTERRUPTIONS = 2;
+ /** @hide */ public static final int ZEN_MODE_ALARMS = 3;
/** @hide */ public static String zenModeToString(int mode) {
if (mode == ZEN_MODE_IMPORTANT_INTERRUPTIONS) return "ZEN_MODE_IMPORTANT_INTERRUPTIONS";
+ if (mode == ZEN_MODE_ALARMS) return "ZEN_MODE_ALARMS";
if (mode == ZEN_MODE_NO_INTERRUPTIONS) return "ZEN_MODE_NO_INTERRUPTIONS";
return "ZEN_MODE_OFF";
}
diff --git a/core/java/android/provider/VoicemailContract.java b/core/java/android/provider/VoicemailContract.java
index 0da4fd5..efbb3b8 100644
--- a/core/java/android/provider/VoicemailContract.java
+++ b/core/java/android/provider/VoicemailContract.java
@@ -258,7 +258,7 @@ public class VoicemailContract {
public static Uri insert(Context context, Voicemail voicemail) {
ContentResolver contentResolver = context.getContentResolver();
ContentValues contentValues = getContentValues(voicemail);
- return contentResolver.insert(Voicemails.CONTENT_URI, contentValues);
+ return contentResolver.insert(buildSourceUri(context.getPackageName()), contentValues);
}
/**
@@ -273,7 +273,7 @@ public class VoicemailContract {
int count = voicemails.size();
for (int i = 0; i < count; i++) {
ContentValues contentValues = getContentValues(voicemails.get(i));
- contentResolver.insert(Voicemails.CONTENT_URI, contentValues);
+ contentResolver.insert(buildSourceUri(context.getPackageName()), contentValues);
}
return count;
}
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index d24bc13..579cdbe 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -60,8 +60,8 @@ interface IKeystoreService {
// Keymaster 0.4 methods
int addRngEntropy(in byte[] data);
- int generateKey(String alias, in KeymasterArguments arguments, int uid, int flags,
- out KeyCharacteristics characteristics);
+ int generateKey(String alias, in KeymasterArguments arguments, in byte[] entropy, int uid,
+ int flags, out KeyCharacteristics characteristics);
int getKeyCharacteristics(String alias, in KeymasterBlob clientId, in KeymasterBlob appId,
out KeyCharacteristics characteristics);
int importKey(String alias, in KeymasterArguments arguments, int format,
@@ -69,8 +69,10 @@ interface IKeystoreService {
ExportResult exportKey(String alias, int format, in KeymasterBlob clientId,
in KeymasterBlob appId);
OperationResult begin(IBinder appToken, String alias, int purpose, boolean pruneable,
- in KeymasterArguments params, out KeymasterArguments operationParams);
+ in KeymasterArguments params, in byte[] entropy, out KeymasterArguments operationParams);
OperationResult update(IBinder token, in KeymasterArguments params, in byte[] input);
OperationResult finish(IBinder token, in KeymasterArguments params, in byte[] signature);
int abort(IBinder handle);
+ boolean isOperationAuthorized(IBinder token);
+ int addAuthToken(in byte[] authToken);
}
diff --git a/core/java/android/security/NetworkSecurityPolicy.java b/core/java/android/security/NetworkSecurityPolicy.java
index 0626bbc..0b3bf45 100644
--- a/core/java/android/security/NetworkSecurityPolicy.java
+++ b/core/java/android/security/NetworkSecurityPolicy.java
@@ -24,8 +24,6 @@ package android.security;
*
* <p>The policy currently consists of a single flag: whether cleartext network traffic is
* permitted. See {@link #isCleartextTrafficPermitted()}.
- *
- * @hide
*/
public class NetworkSecurityPolicy {
@@ -48,9 +46,9 @@ public class NetworkSecurityPolicy {
* without TLS or STARTTLS) is permitted for this process.
*
* <p>When cleartext network traffic is not permitted, the platform's components (e.g. HTTP and
- * FTP stacks, {@code WebView}, {@code MediaPlayer}) will refuse this process's requests to use
- * cleartext traffic. Third-party libraries are strongly encouraged to honor this setting as
- * well.
+ * FTP stacks, {@link android.webkit.WebView}, {@link android.media.MediaPlayer}) will refuse
+ * this process's requests to use cleartext traffic. Third-party libraries are strongly
+ * encouraged to honor this setting as well.
*
* <p>This flag is honored on a best effort basis because it's impossible to prevent all
* cleartext traffic from Android applications given the level of access provided to them. For
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index e653b74..5e2accd 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -16,6 +16,9 @@
package android.security.keymaster;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Class tracking all the keymaster enum values needed for the binder API to keystore.
* This must be kept in sync with hardware/libhardware/include/hardware/keymaster_defs.h
@@ -168,6 +171,10 @@ public final class KeymasterDefs {
public static final int KM_KEY_FORMAT_PKCS12 = 2;
public static final int KM_KEY_FORMAT_RAW = 3;
+ // User authenticators.
+ public static final int HW_AUTH_PASSWORD = 1 << 0;
+ public static final int HW_AUTH_FINGERPRINT = 1 << 1;
+
// Error codes.
public static final int KM_ERROR_OK = 0;
public static final int KM_ERROR_ROOT_OF_TRUST_ALREADY_SET = -1;
@@ -178,7 +185,7 @@ public final class KeymasterDefs {
public static final int KM_ERROR_UNSUPPORTED_KEY_SIZE = -6;
public static final int KM_ERROR_UNSUPPORTED_BLOCK_MODE = -7;
public static final int KM_ERROR_INCOMPATIBLE_BLOCK_MODE = -8;
- public static final int KM_ERROR_UNSUPPORTED_TAG_LENGTH = -9;
+ public static final int KM_ERROR_UNSUPPORTED_MAC_LENGTH = -9;
public static final int KM_ERROR_UNSUPPORTED_PADDING_MODE = -10;
public static final int KM_ERROR_INCOMPATIBLE_PADDING_MODE = -11;
public static final int KM_ERROR_UNSUPPORTED_DIGEST = -12;
@@ -224,7 +231,54 @@ public final class KeymasterDefs {
public static final int KM_ERROR_VERSION_MISMATCH = -101;
public static final int KM_ERROR_UNKNOWN_ERROR = -1000;
+ public static final Map<Integer, String> sErrorCodeToString = new HashMap<Integer, String>();
+ static {
+ sErrorCodeToString.put(KM_ERROR_OK, "OK");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PURPOSE, "Unsupported purpose");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PURPOSE, "Incompatible purpose");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_ALGORITHM, "Unsupported algorithm");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_ALGORITHM, "Incompatible algorithm");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_SIZE, "Unsupported key size");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_BLOCK_MODE, "Unsupported block mode");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_BLOCK_MODE, "Incompatible block mode");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_MAC_LENGTH,
+ "Unsupported MAC or authentication tag length");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_PADDING_MODE, "Unsupported padding mode");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_PADDING_MODE, "Incompatible padding mode");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_DIGEST, "Unsupported digest");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_DIGEST, "Incompatible digest");
+ sErrorCodeToString.put(KM_ERROR_INVALID_EXPIRATION_TIME, "Invalid expiration time");
+ sErrorCodeToString.put(KM_ERROR_INVALID_USER_ID, "Invalid user ID");
+ sErrorCodeToString.put(KM_ERROR_INVALID_AUTHORIZATION_TIMEOUT,
+ "Invalid user authorization timeout");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_KEY_FORMAT, "Unsupported key format");
+ sErrorCodeToString.put(KM_ERROR_INCOMPATIBLE_KEY_FORMAT, "Incompatible key format");
+ sErrorCodeToString.put(KM_ERROR_INVALID_INPUT_LENGTH, "Invalid input length");
+ sErrorCodeToString.put(KM_ERROR_KEY_NOT_YET_VALID, "Key not yet valid");
+ sErrorCodeToString.put(KM_ERROR_KEY_EXPIRED, "Key expired");
+ sErrorCodeToString.put(KM_ERROR_KEY_USER_NOT_AUTHENTICATED, "Key user not authenticated");
+ sErrorCodeToString.put(KM_ERROR_INVALID_OPERATION_HANDLE, "Invalid operation handle");
+ sErrorCodeToString.put(KM_ERROR_VERIFICATION_FAILED, "Signature/MAC verification failed");
+ sErrorCodeToString.put(KM_ERROR_TOO_MANY_OPERATIONS, "Too many operations");
+ sErrorCodeToString.put(KM_ERROR_INVALID_KEY_BLOB, "Invalid key blob");
+ sErrorCodeToString.put(KM_ERROR_INVALID_ARGUMENT, "Invalid argument");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_TAG, "Unsupported tag");
+ sErrorCodeToString.put(KM_ERROR_INVALID_TAG, "Invalid tag");
+ sErrorCodeToString.put(KM_ERROR_MEMORY_ALLOCATION_FAILED, "Memory allocation failed");
+ sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field");
+ sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented");
+ sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error");
+ }
+
public static int getTagType(int tag) {
return tag & (0xF << 28);
}
+
+ public static String getErrorMessage(int errorCode) {
+ String result = sErrorCodeToString.get(errorCode);
+ if (result != null) {
+ return result;
+ }
+ return String.valueOf(errorCode);
+ }
}
diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java
index 4fc9d24..9b46ad3 100644
--- a/core/java/android/security/keymaster/OperationResult.java
+++ b/core/java/android/security/keymaster/OperationResult.java
@@ -28,6 +28,7 @@ import android.os.Parcelable;
public class OperationResult implements Parcelable {
public final int resultCode;
public final IBinder token;
+ public final long operationHandle;
public final int inputConsumed;
public final byte[] output;
@@ -45,6 +46,7 @@ public class OperationResult implements Parcelable {
protected OperationResult(Parcel in) {
resultCode = in.readInt();
token = in.readStrongBinder();
+ operationHandle = in.readLong();
inputConsumed = in.readInt();
output = in.createByteArray();
}
@@ -58,6 +60,7 @@ public class OperationResult implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeInt(resultCode);
out.writeStrongBinder(token);
+ out.writeLong(operationHandle);
out.writeInt(inputConsumed);
out.writeByteArray(output);
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 0860153..fa782e4 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -77,6 +77,12 @@ public abstract class NotificationListenerService extends Service {
*/
public static final int INTERRUPTION_FILTER_NONE = 3;
+ /**
+ * {@link #getCurrentInterruptionFilter() Interruption filter} constant -
+ * Alarms only interruption filter.
+ */
+ public static final int INTERRUPTION_FILTER_ALARMS = 4;
+
/** {@link #getCurrentInterruptionFilter() Interruption filter} constant - returned when
* the value is unavailable for any reason. For example, before the notification listener
* is connected.
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 979a01b..2702457 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -66,6 +66,7 @@ public class ZenModeConfig implements Parcelable {
private static final int MINUTES_MS = 60 * SECONDS_MS;
private static final int ZERO_VALUE_MS = 10 * SECONDS_MS;
+ private static final boolean DEFAULT_ALLOW_REMINDERS = true;
private static final boolean DEFAULT_ALLOW_EVENTS = true;
private static final int XML_VERSION = 1;
@@ -75,6 +76,7 @@ public class ZenModeConfig implements Parcelable {
private static final String ALLOW_ATT_CALLS = "calls";
private static final String ALLOW_ATT_MESSAGES = "messages";
private static final String ALLOW_ATT_FROM = "from";
+ private static final String ALLOW_ATT_REMINDERS = "reminders";
private static final String ALLOW_ATT_EVENTS = "events";
private static final String SLEEP_TAG = "sleep";
private static final String SLEEP_ATT_MODE = "mode";
@@ -100,6 +102,7 @@ public class ZenModeConfig implements Parcelable {
public boolean allowCalls;
public boolean allowMessages;
+ public boolean allowReminders = DEFAULT_ALLOW_REMINDERS;
public boolean allowEvents = DEFAULT_ALLOW_EVENTS;
public int allowFrom = SOURCE_ANYONE;
@@ -119,6 +122,7 @@ public class ZenModeConfig implements Parcelable {
public ZenModeConfig(Parcel source) {
allowCalls = source.readInt() == 1;
allowMessages = source.readInt() == 1;
+ allowReminders = source.readInt() == 1;
allowEvents = source.readInt() == 1;
if (source.readInt() == 1) {
sleepMode = source.readString();
@@ -147,6 +151,7 @@ public class ZenModeConfig implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(allowCalls ? 1 : 0);
dest.writeInt(allowMessages ? 1 : 0);
+ dest.writeInt(allowReminders ? 1 : 0);
dest.writeInt(allowEvents ? 1 : 0);
if (sleepMode != null) {
dest.writeInt(1);
@@ -182,6 +187,7 @@ public class ZenModeConfig implements Parcelable {
.append("allowCalls=").append(allowCalls)
.append(",allowMessages=").append(allowMessages)
.append(",allowFrom=").append(sourceToString(allowFrom))
+ .append(",allowReminders=").append(allowReminders)
.append(",allowEvents=").append(allowEvents)
.append(",sleepMode=").append(sleepMode)
.append(",sleepStart=").append(sleepStartHour).append('.').append(sleepStartMinute)
@@ -217,6 +223,7 @@ public class ZenModeConfig implements Parcelable {
return other.allowCalls == allowCalls
&& other.allowMessages == allowMessages
&& other.allowFrom == allowFrom
+ && other.allowReminders == allowReminders
&& other.allowEvents == allowEvents
&& Objects.equals(other.sleepMode, sleepMode)
&& other.sleepNone == sleepNone
@@ -232,9 +239,9 @@ public class ZenModeConfig implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(allowCalls, allowMessages, allowFrom, allowEvents, sleepMode, sleepNone,
- sleepStartHour, sleepStartMinute, sleepEndHour, sleepEndMinute,
- Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds),
+ return Objects.hash(allowCalls, allowMessages, allowFrom, allowReminders, allowEvents,
+ sleepMode, sleepNone, sleepStartHour, sleepStartMinute, sleepEndHour,
+ sleepEndMinute, Arrays.hashCode(conditionComponents), Arrays.hashCode(conditionIds),
exitCondition, exitConditionComponent);
}
@@ -300,6 +307,8 @@ public class ZenModeConfig implements Parcelable {
if (ALLOW_TAG.equals(tag)) {
rt.allowCalls = safeBoolean(parser, ALLOW_ATT_CALLS, false);
rt.allowMessages = safeBoolean(parser, ALLOW_ATT_MESSAGES, false);
+ rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS,
+ DEFAULT_ALLOW_REMINDERS);
rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS);
rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE);
if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) {
@@ -344,6 +353,7 @@ public class ZenModeConfig implements Parcelable {
out.startTag(null, ALLOW_TAG);
out.attribute(null, ALLOW_ATT_CALLS, Boolean.toString(allowCalls));
out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages));
+ out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders));
out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents));
out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom));
out.endTag(null, ALLOW_TAG);
diff --git a/core/java/android/service/voice/IVoiceInteractionSession.aidl b/core/java/android/service/voice/IVoiceInteractionSession.aidl
index 4f4b2d5..7c90261 100644
--- a/core/java/android/service/voice/IVoiceInteractionSession.aidl
+++ b/core/java/android/service/voice/IVoiceInteractionSession.aidl
@@ -20,11 +20,13 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
+
/**
* @hide
*/
oneway interface IVoiceInteractionSession {
- void show(in Bundle sessionArgs, int flags);
+ void show(in Bundle sessionArgs, int flags, IVoiceInteractionSessionShowCallback showCallback);
void hide();
void handleAssist(in Bundle assistData);
void handleScreenshot(in Bitmap screenshot);
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 419b92b..fee0c75 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -82,6 +82,12 @@ public class VoiceInteractionService extends Service {
*/
public static final int START_WITH_SCREENSHOT = 1<<1;
+ /**
+ * Flag for use with {@link #showSession}: indicate that the session has been started from the
+ * system assist gesture.
+ */
+ public static final int START_SOURCE_ASSIST_GESTURE = 1<<2;
+
IVoiceInteractionService mInterface = new IVoiceInteractionService.Stub() {
@Override public void ready() {
mHandler.sendEmptyMessage(MSG_READY);
diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
index ebc7507..4bc97c9 100644
--- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java
+++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java
@@ -43,6 +43,7 @@ public class VoiceInteractionServiceInfo {
private String mSessionService;
private String mRecognitionService;
private String mSettingsActivity;
+ private boolean mSupportsAssistGesture;
public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp)
throws PackageManager.NameNotFoundException {
@@ -94,6 +95,9 @@ public class VoiceInteractionServiceInfo {
com.android.internal.R.styleable.VoiceInteractionService_recognitionService);
mSettingsActivity = array.getString(
com.android.internal.R.styleable.VoiceInteractionService_settingsActivity);
+ mSupportsAssistGesture = array.getBoolean(
+ com.android.internal.R.styleable.VoiceInteractionService_supportsAssistGesture,
+ false);
array.recycle();
if (mSessionService == null) {
mParseError = "No sessionService specified";
@@ -103,11 +107,6 @@ public class VoiceInteractionServiceInfo {
mParseError = "No recognitionService specified";
return;
}
- /* Not yet time
- if (mRecognitionService == null) {
- mParseError = "No recogitionService specified";
- return;
- } */
} catch (XmlPullParserException e) {
mParseError = "Error parsing voice interation service meta-data: " + e;
Log.w(TAG, "error parsing voice interaction service meta-data", e);
@@ -145,4 +144,8 @@ public class VoiceInteractionServiceInfo {
public String getSettingsActivity() {
return mSettingsActivity;
}
+
+ public boolean getSupportsAssistGesture() {
+ return mSupportsAssistGesture;
+ }
}
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 7a5bb90..20d7079 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -16,6 +16,7 @@
package android.service.voice;
+import android.app.AssistStructure;
import android.app.Dialog;
import android.app.Instrumentation;
import android.app.VoiceInteractor;
@@ -43,6 +44,7 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.FrameLayout;
import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.IVoiceInteractorCallback;
import com.android.internal.app.IVoiceInteractorRequest;
@@ -163,9 +165,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
final IVoiceInteractionSession mSession = new IVoiceInteractionSession.Stub() {
@Override
- public void show(Bundle sessionArgs, int flags) {
- mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIO(MSG_SHOW,
- flags, sessionArgs));
+ public void show(Bundle sessionArgs, int flags,
+ IVoiceInteractionSessionShowCallback showCallback) {
+ mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageIOO(MSG_SHOW,
+ flags, sessionArgs, showCallback));
}
@Override
@@ -175,6 +178,18 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
@Override
public void handleAssist(Bundle assistBundle) {
+ // We want to pre-warm the AssistStructure before handing it off to the main
+ // thread. There is a strong argument to be made that it should be handed
+ // through as a separate param rather than part of the assistBundle.
+ if (assistBundle != null) {
+ Bundle assistContext = assistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT);
+ if (assistContext != null) {
+ AssistStructure as = AssistStructure.getAssistStructure(assistContext);
+ if (as != null) {
+ as.ensureData();
+ }
+ }
+ }
mHandlerCaller.sendMessage(mHandlerCaller.obtainMessageO(MSG_HANDLE_ASSIST,
assistBundle));
}
@@ -412,9 +427,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
onHandleScreenshot((Bitmap) msg.obj);
break;
case MSG_SHOW:
- if (DEBUG) Log.d(TAG, "doShow: args=" + msg.obj
- + " flags=" + msg.arg1);
- doShow((Bundle) msg.obj, msg.arg1);
+ args = (SomeArgs)msg.obj;
+ if (DEBUG) Log.d(TAG, "doShow: args=" + args.arg1
+ + " flags=" + msg.arg1
+ + " showCallback=" + args.arg2);
+ doShow((Bundle) args.arg1, msg.arg1,
+ (IVoiceInteractionSessionShowCallback) args.arg2);
break;
case MSG_HIDE:
if (DEBUG) Log.d(TAG, "doHide");
@@ -502,6 +520,10 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
mCallbacks, true);
}
+ public Context getContext() {
+ return mContext;
+ }
+
Request newRequest(IVoiceInteractorCallback callback) {
synchronized (this) {
Request req = new Request(callback, this);
@@ -512,11 +534,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
Request removeRequest(IBinder reqInterface) {
synchronized (this) {
- Request req = mActiveRequests.get(reqInterface);
- if (req != null) {
- mActiveRequests.remove(req);
- }
- return req;
+ return mActiveRequests.remove(reqInterface);
}
}
@@ -527,7 +545,7 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
onCreate(args, startFlags);
}
- void doShow(Bundle args, int flags) {
+ void doShow(Bundle args, int flags, final IVoiceInteractionSessionShowCallback showCallback) {
if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded
+ " mWindowVisible=" + mWindowVisible);
@@ -552,6 +570,22 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
mWindowVisible = true;
mWindow.show();
}
+ if (showCallback != null) {
+ mRootView.invalidate();
+ mRootView.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mRootView.getViewTreeObserver().removeOnPreDrawListener(this);
+ try {
+ showCallback.onShown();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling onShown", e);
+ }
+ return true;
+ }
+ });
+ }
} finally {
mWindowWasVisible = true;
mInShowWindow = false;
@@ -586,7 +620,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
mRootView = mInflater.inflate(
com.android.internal.R.layout.voice_interaction_session, null);
mRootView.setSystemUiVisibility(
- View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
mWindow.setContentView(mRootView);
mRootView.getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsComputer);
@@ -720,7 +755,9 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
mWindow = new SoftInputWindow(mContext, "VoiceInteractionSession", mTheme,
mCallbacks, this, mDispatcherState,
WindowManager.LayoutParams.TYPE_VOICE_INTERACTION, Gravity.BOTTOM, true);
- mWindow.getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ mWindow.getWindow().addFlags(
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED |
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN);
initViews();
mWindow.getWindow().setLayout(MATCH_PARENT, MATCH_PARENT);
mWindow.setToken(mToken);
@@ -799,6 +836,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
return false;
}
+ /**
+ * Called when the user presses the back button while focus is in the session UI. Note
+ * that this will only happen if the session UI has requested input focus in its window;
+ * otherwise, the back key will go to whatever window has focus and do whatever behavior
+ * it normally has there.
+ */
public void onBackPressed() {
hide();
}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 1bdaef0..239b386 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -79,7 +79,8 @@ public class DynamicLayout extends Layout
boolean includepad,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
- spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth);
+ spacingmult, spacingadd, includepad, StaticLayout.BREAK_STRATEGY_SIMPLE,
+ ellipsize, ellipsizedWidth);
}
/**
@@ -95,7 +96,7 @@ public class DynamicLayout extends Layout
TextPaint paint,
int width, Alignment align, TextDirectionHeuristic textDir,
float spacingmult, float spacingadd,
- boolean includepad,
+ boolean includepad, int breakStrategy,
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
super((ellipsize == null)
? display
@@ -120,6 +121,7 @@ public class DynamicLayout extends Layout
mObjects = new PackedObjectVector<Directions>(1);
mIncludePad = includepad;
+ mBreakStrategy = breakStrategy;
/*
* This is annoying, but we can't refer to the layout until
@@ -279,10 +281,9 @@ public class DynamicLayout extends Layout
sBuilder = null;
}
- // TODO: make sure reflowed is properly initialized
if (reflowed == null) {
reflowed = new StaticLayout(null);
- b = StaticLayout.Builder.obtain();
+ b = StaticLayout.Builder.obtain(text, where, where + after, getWidth());
}
b.setText(text, where, where + after)
@@ -292,7 +293,8 @@ public class DynamicLayout extends Layout
.setSpacingMult(getSpacingMultiplier())
.setSpacingAdd(getSpacingAdd())
.setEllipsizedWidth(mEllipsizedWidth)
- .setEllipsize(mEllipsizeAt);
+ .setEllipsize(mEllipsizeAt)
+ .setBreakStrategy(mBreakStrategy);
reflowed.generate(b, false, true);
int n = reflowed.getLineCount();
@@ -356,6 +358,8 @@ public class DynamicLayout extends Layout
ints[DESCENT] = desc;
objects[0] = reflowed.getLineDirections(i);
+ ints[HYPHEN] = reflowed.getHyphen(i);
+
if (mEllipsize) {
ints[ELLIPSIS_START] = reflowed.getEllipsisStart(i);
ints[ELLIPSIS_COUNT] = reflowed.getEllipsisCount(i);
@@ -631,6 +635,14 @@ public class DynamicLayout extends Layout
return mBottomPadding;
}
+ /**
+ * @hide
+ */
+ @Override
+ public int getHyphen(int line) {
+ return mInts.getValue(line, HYPHEN);
+ }
+
@Override
public int getEllipsizedWidth() {
return mEllipsizedWidth;
@@ -707,6 +719,7 @@ public class DynamicLayout extends Layout
private boolean mEllipsize;
private int mEllipsizedWidth;
private TextUtils.TruncateAt mEllipsizeAt;
+ private int mBreakStrategy;
private PackedIntVector mInts;
private PackedObjectVector<Directions> mObjects;
@@ -739,11 +752,12 @@ public class DynamicLayout extends Layout
private static final int TAB = START;
private static final int TOP = 1;
private static final int DESCENT = 2;
- private static final int COLUMNS_NORMAL = 3;
+ private static final int HYPHEN = 3;
+ private static final int COLUMNS_NORMAL = 4;
- private static final int ELLIPSIS_START = 3;
- private static final int ELLIPSIS_COUNT = 4;
- private static final int COLUMNS_ELLIPSIZE = 5;
+ private static final int ELLIPSIS_START = 4;
+ private static final int ELLIPSIS_COUNT = 5;
+ private static final int COLUMNS_ELLIPSIZE = 6;
private static final int START_MASK = 0x1FFFFFFF;
private static final int DIR_SHIFT = 30;
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
new file mode 100644
index 0000000..a99bdf5
--- /dev/null
+++ b/core/java/android/text/Hyphenator.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.HashMap;
+import java.util.Locale;
+
+/**
+ * Hyphenator is a wrapper class for a native implementation of automatic hyphenation,
+ * in essence finding valid hyphenation opportunities in a word.
+ *
+ * @hide
+ */
+public class Hyphenator {
+ // This class has deliberately simple lifetime management (no finalizer) because in
+ // the common case a process will use a very small number of locales.
+
+ private static String TAG = "Hyphenator";
+
+ static HashMap<Locale, Hyphenator> sMap = new HashMap<Locale, Hyphenator>();
+
+ private long mNativePtr;
+
+ private Hyphenator(long nativePtr) {
+ mNativePtr = nativePtr;
+ }
+
+ public static long get(Locale locale) {
+ Hyphenator result = sMap.get(locale);
+ return result == null ? 0 : result.mNativePtr;
+ }
+
+ private static Hyphenator loadHyphenator(Locale locale) {
+ // TODO: find pattern dictionary (from system location) that best matches locale
+ if (Locale.US.equals(locale)) {
+ File f = new File(getSystemHyphenatorLocation(), "hyph-en-us.pat.txt");
+ try {
+ RandomAccessFile rf = new RandomAccessFile(f, "r");
+ byte[] buf = new byte[(int)rf.length()];
+ rf.read(buf);
+ rf.close();
+ String patternData = new String(buf);
+ long nativePtr = StaticLayout.nLoadHyphenator(patternData);
+ return new Hyphenator(nativePtr);
+ } catch (IOException e) {
+ Log.e(TAG, "error loading hyphenation " + f, e);
+ }
+ }
+ return null;
+ }
+
+ private static File getSystemHyphenatorLocation() {
+ // TODO: move to a sensible location under system
+ return new File("/system/usr/hyphen-data");
+ }
+
+ /**
+ * Load hyphenation patterns at initialization time. We want to have patterns
+ * for all locales loaded and ready to use so we don't have to do any file IO
+ * on the UI thread when drawing text in different locales.
+ *
+ * @hide
+ */
+ public static void init() {
+ Locale l = Locale.US;
+ sMap.put(l, loadHyphenator(l));
+ }
+}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index fcf1828..16ae5e2 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,6 +16,7 @@
package android.text;
+import android.annotation.IntDef;
import android.emoji.EmojiFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -33,6 +34,8 @@ import android.text.style.TabStopSpan;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
@@ -43,6 +46,31 @@ import java.util.Arrays;
* For text that will not change, use a {@link StaticLayout}.
*/
public abstract class Layout {
+ /** @hide */
+ @IntDef({BREAK_STRATEGY_SIMPLE, BREAK_STRATEGY_HIGH_QUALITY, BREAK_STRATEGY_BALANCED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BreakStrategy {}
+
+ /**
+ * Value for break strategy indicating simple line breaking. Automatic hyphens are not added
+ * (though soft hyphens are respected), and modifying text generally doesn't affect the layout
+ * before it (which yields a more consistent user experience when editing), but layout may not
+ * be the highest quality.
+ */
+ public static final int BREAK_STRATEGY_SIMPLE = 0;
+
+ /**
+ * Value for break strategy indicating high quality line breaking, including automatic
+ * hyphenation and doing whole-paragraph optimization of line breaks.
+ */
+ public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;
+
+ /**
+ * Value for break strategy indicating balanced line breaking. The breaks are chosen to
+ * make all lines as close to the same length as possible, including automatic hyphenation.
+ */
+ public static final int BREAK_STRATEGY_BALANCED = 2;
+
private static final ParagraphStyle[] NO_PARA_SPANS =
ArrayUtils.emptyArray(ParagraphStyle.class);
@@ -225,17 +253,17 @@ public abstract class Layout {
// Draw the lines, one at a time.
// The baseline is the top of the following line minus the current line's descent.
- for (int i = firstLine; i <= lastLine; i++) {
+ for (int lineNum = firstLine; lineNum <= lastLine; lineNum++) {
int start = previousLineEnd;
- previousLineEnd = getLineStart(i + 1);
- int end = getLineVisibleEnd(i, start, previousLineEnd);
+ previousLineEnd = getLineStart(lineNum + 1);
+ int end = getLineVisibleEnd(lineNum, start, previousLineEnd);
int ltop = previousLineBottom;
- int lbottom = getLineTop(i+1);
+ int lbottom = getLineTop(lineNum + 1);
previousLineBottom = lbottom;
- int lbaseline = lbottom - getLineDescent(i);
+ int lbaseline = lbottom - getLineDescent(lineNum);
- int dir = getParagraphDirection(i);
+ int dir = getParagraphDirection(lineNum);
int left = 0;
int right = mWidth;
@@ -254,7 +282,7 @@ public abstract class Layout {
// just collect the ones present at the start of the paragraph.
// If spanEnd is before the end of the paragraph, that's not
// our problem.
- if (start >= spanEnd && (i == firstLine || isFirstParaLine)) {
+ if (start >= spanEnd && (lineNum == firstLine || isFirstParaLine)) {
spanEnd = sp.nextSpanTransition(start, textLength,
ParagraphStyle.class);
spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
@@ -280,7 +308,7 @@ public abstract class Layout {
int startLine = getLineForOffset(sp.getSpanStart(spans[n]));
// if there is more than one LeadingMarginSpan2, use
// the count that is greatest
- if (i < startLine + count) {
+ if (lineNum < startLine + count) {
useFirstLineMargin = true;
break;
}
@@ -304,7 +332,7 @@ public abstract class Layout {
}
}
- boolean hasTabOrEmoji = getLineContainsTab(i);
+ boolean hasTabOrEmoji = getLineContainsTab(lineNum);
// Can't tell if we have tabs for sure, currently
if (hasTabOrEmoji && !tabStopsIsInitialized) {
if (tabStops == null) {
@@ -333,7 +361,7 @@ public abstract class Layout {
x = right;
}
} else {
- int max = (int)getLineExtent(i, tabStops, false);
+ int max = (int)getLineExtent(lineNum, tabStops, false);
if (align == Alignment.ALIGN_OPPOSITE) {
if (dir == DIR_LEFT_TO_RIGHT) {
x = right - max;
@@ -346,7 +374,8 @@ public abstract class Layout {
}
}
- Directions directions = getLineDirections(i);
+ paint.setHyphenEdit(getHyphen(lineNum));
+ Directions directions = getLineDirections(lineNum);
if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {
// XXX: assumes there's nothing additional to be done
canvas.drawText(buf, start, end, x, lbaseline, paint);
@@ -677,6 +706,15 @@ public abstract class Layout {
*/
public abstract int getBottomPadding();
+ /**
+ * Returns the hyphen edit for a line.
+ *
+ * @hide
+ */
+ public int getHyphen(int line) {
+ return 0;
+ }
+
/**
* Returns true if the character at offset and the preceding character
@@ -1153,7 +1191,10 @@ public abstract class Layout {
return end - 1;
}
- if (ch != ' ' && ch != '\t') {
+ // Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
+ if (!(ch == ' ' || ch == '\t' || ch == 0x1680 ||
+ (0x2000 <= ch && ch <= 0x200A && ch != 0x2007) ||
+ ch == 0x205F || ch == 0x3000)) {
break;
}
diff --git a/core/java/android/text/SpanSet.java b/core/java/android/text/SpanSet.java
index 3ca6033..00f1493 100644
--- a/core/java/android/text/SpanSet.java
+++ b/core/java/android/text/SpanSet.java
@@ -17,6 +17,7 @@
package android.text;
import java.lang.reflect.Array;
+import java.util.Arrays;
/**
* A cached set of spans. Caches the result of {@link Spanned#getSpans(int, int, Class)} and then
@@ -54,6 +55,7 @@ public class SpanSet<E> {
spanFlags = new int[length];
}
+ int prevNumberOfSpans = numberOfSpans;
numberOfSpans = 0;
for (int i = 0; i < length; i++) {
final E span = allSpans[i];
@@ -71,6 +73,12 @@ public class SpanSet<E> {
numberOfSpans++;
}
+
+ // cleanup extra spans left over from previous init() call
+ if (numberOfSpans < prevNumberOfSpans) {
+ // prevNumberofSpans was > 0, therefore spans != null
+ Arrays.fill(spans, numberOfSpans, prevNumberOfSpans, null);
+ }
}
/**
@@ -103,9 +111,8 @@ public class SpanSet<E> {
* Removes all internal references to the spans to avoid memory leaks.
*/
public void recycle() {
- // The spans array is guaranteed to be not null when numberOfSpans is > 0
- for (int i = 0; i < numberOfSpans; i++) {
- spans[i] = null; // prevent a leak: no reference kept when TextLine is recycled
+ if (spans != null) {
+ Arrays.fill(spans, 0, numberOfSpans, null);
}
}
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index ee39e27..2bcb352 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -23,6 +23,7 @@ import android.text.style.LineHeightSpan;
import android.text.style.MetricAffectingSpan;
import android.text.style.TabStopSpan;
import android.util.Log;
+import android.util.Pools.SynchronizedPool;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -56,28 +57,23 @@ public class StaticLayout extends Layout {
mNativePtr = nNewBuilder();
}
- static Builder obtain() {
- Builder b = null;
- synchronized (sLock) {
- for (int i = 0; i < sCached.length; i++) {
- if (sCached[i] != null) {
- b = sCached[i];
- sCached[i] = null;
- break;
- }
- }
- }
+ public static Builder obtain(CharSequence source, int start, int end, int width) {
+ Builder b = sPool.acquire();
if (b == null) {
b = new Builder();
}
// set default initial values
- b.mWidth = 0;
+ b.mText = source;
+ b.mStart = start;
+ b.mEnd = end;
+ b.mWidth = width;
+ b.mAlignment = Alignment.ALIGN_NORMAL;
b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
b.mSpacingMult = 1.0f;
b.mSpacingAdd = 0.0f;
b.mIncludePad = true;
- b.mEllipsizedWidth = 0;
+ b.mEllipsizedWidth = width;
b.mEllipsize = null;
b.mMaxLines = Integer.MAX_VALUE;
@@ -85,18 +81,11 @@ public class StaticLayout extends Layout {
return b;
}
- static void recycle(Builder b) {
+ private static void recycle(Builder b) {
b.mPaint = null;
b.mText = null;
MeasuredText.recycle(b.mMeasuredText);
- synchronized (sLock) {
- for (int i = 0; i < sCached.length; i++) {
- if (sCached[i] == null) {
- sCached[i] = b;
- break;
- }
- }
- }
+ sPool.release(b);
}
// release any expensive state
@@ -129,6 +118,11 @@ public class StaticLayout extends Layout {
return this;
}
+ public Builder setAlignment(Alignment alignment) {
+ mAlignment = alignment;
+ return this;
+ }
+
public Builder setTextDir(TextDirectionHeuristic textDir) {
mTextDir = textDir;
return this;
@@ -166,12 +160,19 @@ public class StaticLayout extends Layout {
return this;
}
+ public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
+ mBreakStrategy = breakStrategy;
+ return this;
+ }
+
/**
* Measurement and break iteration is done in native code. The protocol for using
* the native code is as follows.
*
- * For each paragraph, do a nSetText of the paragraph text. Then, for each run within the
- * paragraph:
+ * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
+ * stops, break strategy (and possibly other parameters in the future).
+ *
+ * Then, for each run within the paragraph:
* - setLocale (this must be done at least for the first run, optional afterwards)
* - one of the following, depending on the type of run:
* + addStyleRun (a text run, to be measured in native code)
@@ -186,7 +187,7 @@ public class StaticLayout extends Layout {
private void setLocale(Locale locale) {
if (!locale.equals(mLocale)) {
- nSetLocale(mNativePtr, locale.toLanguageTag());
+ nSetLocale(mNativePtr, locale.toLanguageTag(), Hyphenator.get(locale));
mLocale = locale;
}
}
@@ -205,10 +206,8 @@ public class StaticLayout extends Layout {
}
public StaticLayout build() {
- // TODO: can optimize based on whether ellipsis is needed
- StaticLayout result = new StaticLayout(mText);
- result.generate(this, this.mIncludePad, this.mIncludePad);
- recycle(this);
+ StaticLayout result = new StaticLayout(this);
+ Builder.recycle(this);
return result;
}
@@ -228,6 +227,7 @@ public class StaticLayout extends Layout {
int mEnd;
TextPaint mPaint;
int mWidth;
+ Alignment mAlignment;
TextDirectionHeuristic mTextDir;
float mSpacingMult;
float mSpacingAdd;
@@ -235,6 +235,7 @@ public class StaticLayout extends Layout {
int mEllipsizedWidth;
TextUtils.TruncateAt mEllipsize;
int mMaxLines;
+ int mBreakStrategy;
Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
@@ -243,8 +244,7 @@ public class StaticLayout extends Layout {
Locale mLocale;
- private static final Object sLock = new Object();
- private static final Builder[] sCached = new Builder[3];
+ private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<Builder>(3);
}
public StaticLayout(CharSequence source, TextPaint paint,
@@ -314,10 +314,9 @@ public class StaticLayout extends Layout {
: new Ellipsizer(source),
paint, outerwidth, align, textDir, spacingmult, spacingadd);
- Builder b = Builder.obtain();
- b.setText(source, bufstart, bufend)
+ Builder b = Builder.obtain(source, bufstart, bufend, outerwidth)
.setPaint(paint)
- .setWidth(outerwidth)
+ .setAlignment(align)
.setTextDir(textDir)
.setSpacingMult(spacingmult)
.setSpacingAdd(spacingadd)
@@ -364,6 +363,35 @@ public class StaticLayout extends Layout {
mLines = new int[mLineDirections.length];
}
+ private StaticLayout(Builder b) {
+ super((b.mEllipsize == null)
+ ? b.mText
+ : (b.mText instanceof Spanned)
+ ? new SpannedEllipsizer(b.mText)
+ : new Ellipsizer(b.mText),
+ b.mPaint, b.mWidth, b.mAlignment, b.mSpacingMult, b.mSpacingAdd);
+
+ if (b.mEllipsize != null) {
+ Ellipsizer e = (Ellipsizer) getText();
+
+ e.mLayout = this;
+ e.mWidth = b.mEllipsizedWidth;
+ e.mMethod = b.mEllipsize;
+ mEllipsizedWidth = b.mEllipsizedWidth;
+
+ mColumns = COLUMNS_ELLIPSIZE;
+ } else {
+ mColumns = COLUMNS_NORMAL;
+ mEllipsizedWidth = b.mWidth;
+ }
+
+ mLineDirections = ArrayUtils.newUnpaddedArray(Directions.class, 2 * mColumns);
+ mLines = new int[mLineDirections.length];
+ mMaximumVisibleLineCount = b.mMaxLines;
+
+ generate(b, b.mIncludePad, b.mIncludePad);
+ }
+
/* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
CharSequence source = b.mText;
int bufStart = b.mStart;
@@ -459,7 +487,25 @@ public class StaticLayout extends Layout {
byte[] chdirs = measured.mLevels;
int dir = measured.mDir;
boolean easy = measured.mEasy;
- nSetText(b.mNativePtr, chs, paraEnd - paraStart);
+
+ // tab stop locations
+ int[] variableTabStops = null;
+ if (spanned != null) {
+ TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+ paraEnd, TabStopSpan.class);
+ if (spans.length > 0) {
+ int[] stops = new int[spans.length];
+ for (int i = 0; i < spans.length; i++) {
+ stops[i] = spans[i].getTabStop();
+ }
+ Arrays.sort(stops, 0, stops.length);
+ variableTabStops = stops;
+ }
+ }
+
+ nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
+ firstWidth, firstWidthLineCount, restWidth,
+ variableTabStops, TAB_INCREMENT, b.mBreakStrategy);
// measurement has to be done before performing line breaking
// but we don't want to recompute fontmetrics or span ranges the
@@ -505,29 +551,13 @@ public class StaticLayout extends Layout {
spanEndCacheCount++;
}
- // tab stop locations
- int[] variableTabStops = null;
- if (spanned != null) {
- TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
- paraEnd, TabStopSpan.class);
- if (spans.length > 0) {
- int[] stops = new int[spans.length];
- for (int i = 0; i < spans.length; i++) {
- stops[i] = spans[i].getTabStop();
- }
- Arrays.sort(stops, 0, stops.length);
- variableTabStops = stops;
- }
- }
-
nGetWidths(b.mNativePtr, widths);
- int breakCount = nComputeLineBreaks(b.mNativePtr, paraEnd - paraStart, firstWidth,
- firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks,
- lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
+ int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
+ lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
int[] breaks = lineBreaks.breaks;
float[] lineWidths = lineBreaks.widths;
- boolean[] flags = lineBreaks.flags;
+ int[] flags = lineBreaks.flags;
// here is the offset of the starting character of the line we are currently measuring
int here = paraStart;
@@ -613,7 +643,7 @@ public class StaticLayout extends Layout {
fm.top, fm.bottom,
v,
spacingmult, spacingadd, null,
- null, fm, false,
+ null, fm, 0,
needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
includepad, trackpad, null,
null, bufStart, ellipsize,
@@ -625,7 +655,7 @@ public class StaticLayout extends Layout {
int above, int below, int top, int bottom, int v,
float spacingmult, float spacingadd,
LineHeightSpan[] chooseHt, int[] chooseHtv,
- Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
+ Paint.FontMetricsInt fm, int flags,
boolean needMultiply, byte[] chdirs, int dir,
boolean easy, int bufEnd, boolean includePad,
boolean trackPad, char[] chs,
@@ -718,8 +748,10 @@ public class StaticLayout extends Layout {
lines[off + mColumns + START] = end;
lines[off + mColumns + TOP] = v;
- if (hasTabOrEmoji)
- lines[off + TAB] |= TAB_MASK;
+ // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining
+ // one bit for start field
+ lines[off + TAB] |= flags & TAB_MASK;
+ lines[off + HYPHEN] = flags;
lines[off + DIR] |= dir << DIR_SHIFT;
Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT;
@@ -938,6 +970,14 @@ public class StaticLayout extends Layout {
return mBottomPadding;
}
+ /**
+ * @hide
+ */
+ @Override
+ public int getHyphen(int line) {
+ return mLines[mColumns * line + HYPHEN] & 0xff;
+ }
+
@Override
public int getEllipsisCount(int line) {
if (mColumns < COLUMNS_ELLIPSIZE) {
@@ -964,9 +1004,15 @@ public class StaticLayout extends Layout {
private static native long nNewBuilder();
private static native void nFreeBuilder(long nativePtr);
private static native void nFinishBuilder(long nativePtr);
- private static native void nSetLocale(long nativePtr, String locale);
- private static native void nSetText(long nativePtr, char[] text, int length);
+ /* package */ static native long nLoadHyphenator(String patternData);
+
+ private static native void nSetLocale(long nativePtr, String locale, long nativeHyphenator);
+
+ // Set up paragraph text and settings; done as one big method to minimize jni crossings
+ private static native void nSetupParagraph(long nativePtr, char[] text, int length,
+ float firstWidth, int firstWidthLineCount, float restWidth,
+ int[] variableTabStops, int defaultTabStop, int breakStrategy);
private static native float nAddStyleRun(long nativePtr, long nativePaint,
long nativeTypeface, int start, int end, boolean isRtl);
@@ -983,25 +1029,24 @@ public class StaticLayout extends Layout {
// the arrays inside the LineBreaks objects are passed in as well
// to reduce the number of JNI calls in the common case where the
// arrays do not have to be resized
- private static native int nComputeLineBreaks(long nativePtr,
- int length, float firstWidth, int firstWidthLineCount, float restWidth,
- int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle,
- int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength);
+ private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
+ int[] recycleBreaks, float[] recycleWidths, int[] recycleFlags, int recycleLength);
private int mLineCount;
private int mTopPadding, mBottomPadding;
private int mColumns;
private int mEllipsizedWidth;
- private static final int COLUMNS_NORMAL = 3;
- private static final int COLUMNS_ELLIPSIZE = 5;
+ private static final int COLUMNS_NORMAL = 4;
+ private static final int COLUMNS_ELLIPSIZE = 6;
private static final int START = 0;
private static final int DIR = START;
private static final int TAB = START;
private static final int TOP = 1;
private static final int DESCENT = 2;
- private static final int ELLIPSIS_START = 3;
- private static final int ELLIPSIS_COUNT = 4;
+ private static final int HYPHEN = 3;
+ private static final int ELLIPSIS_START = 4;
+ private static final int ELLIPSIS_COUNT = 5;
private int[] mLines;
private Directions[] mLineDirections;
@@ -1023,7 +1068,7 @@ public class StaticLayout extends Layout {
private static final int INITIAL_SIZE = 16;
public int[] breaks = new int[INITIAL_SIZE];
public float[] widths = new float[INITIAL_SIZE];
- public boolean[] flags = new boolean[INITIAL_SIZE]; // hasTabOrEmoji
+ public int[] flags = new int[INITIAL_SIZE]; // hasTabOrEmoji
// breaks, widths, and flags should all have the same length
}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 4725581..479242c 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -955,6 +955,10 @@ class TextLine {
span.updateDrawState(wp);
}
+ // Only draw hyphen on last run in line
+ if (jnext < mLen) {
+ wp.setHyphenEdit(0);
+ }
x += handleText(wp, j, jnext, i, inext, runIsRtl, c, x,
top, y, bottom, fmi, needWidth || jnext < measureLimit);
}
diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java
index 0c66709..d567d90 100644
--- a/core/java/android/text/format/Time.java
+++ b/core/java/android/text/format/Time.java
@@ -47,6 +47,7 @@ import libcore.util.ZoneInfoDB;
* before 1st Jan 1970 UTC).</li>
* <li>Much of the formatting / parsing assumes ASCII text and is therefore not suitable for
* use with non-ASCII scripts.</li>
+ * <li>No support for pseudo-zones like "GMT-07:00".</li>
* </ul>
*
* @deprecated Use {@link java.util.GregorianCalendar} instead.
diff --git a/core/java/android/transition/Fade.java b/core/java/android/transition/Fade.java
index e7857c0..287c696 100644
--- a/core/java/android/transition/Fade.java
+++ b/core/java/android/transition/Fade.java
@@ -108,7 +108,7 @@ public class Fade extends Visibility {
/**
* Utility method to handle creating and running the Animator.
*/
- private Animator createAnimation(View view, float startAlpha, float endAlpha) {
+ private Animator createAnimation(final View view, float startAlpha, final float endAlpha) {
if (startAlpha == endAlpha) {
return null;
}
@@ -117,9 +117,15 @@ public class Fade extends Visibility {
if (DBG) {
Log.d(LOG_TAG, "Created animator " + anim);
}
- FadeAnimatorListener listener = new FadeAnimatorListener(view);
+ final FadeAnimatorListener listener = new FadeAnimatorListener(view);
anim.addListener(listener);
anim.addPauseListener(listener);
+ addListener(new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ view.setTransitionAlpha(1);
+ }
+ });
return anim;
}
@@ -143,7 +149,6 @@ public class Fade extends Visibility {
private static class FadeAnimatorListener extends AnimatorListenerAdapter {
private final View mView;
- private boolean mCanceled = false;
private float mPausedAlpha = -1;
private boolean mLayerTypeChanged = false;
@@ -160,18 +165,8 @@ public class Fade extends Visibility {
}
@Override
- public void onAnimationCancel(Animator animator) {
- mCanceled = true;
- if (mPausedAlpha >= 0) {
- mView.setTransitionAlpha(mPausedAlpha);
- }
- }
-
- @Override
public void onAnimationEnd(Animator animator) {
- if (!mCanceled) {
- mView.setTransitionAlpha(1);
- }
+ mView.setTransitionAlpha(1);
if (mLayerTypeChanged) {
mView.setLayerType(View.LAYER_TYPE_NONE, null);
}
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index 84d9ce8..c44f42b 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -17,8 +17,10 @@
package android.util;
import java.io.PrintWriter;
-import java.lang.reflect.Method;
+import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.Locale;
/**
@@ -203,4 +205,57 @@ public class DebugUtils {
outBuilder.append(suffix);
return outBuilder.toString();
}
+
+ /**
+ * Use prefixed constants (static final values) on given class to turn value
+ * into human-readable string.
+ *
+ * @hide
+ */
+ public static String valueToString(Class<?> clazz, String prefix, int value) {
+ for (Field field : clazz.getDeclaredFields()) {
+ final int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+ && field.getType().equals(int.class) && field.getName().startsWith(prefix)) {
+ try {
+ if (value == field.getInt(null)) {
+ return field.getName().substring(prefix.length());
+ }
+ } catch (IllegalAccessException ignored) {
+ }
+ }
+ }
+ return Integer.toString(value);
+ }
+
+ /**
+ * Use prefixed constants (static final values) on given class to turn flags
+ * into human-readable string.
+ *
+ * @hide
+ */
+ public static String flagsToString(Class<?> clazz, String prefix, int flags) {
+ final StringBuilder res = new StringBuilder();
+
+ for (Field field : clazz.getDeclaredFields()) {
+ final int modifiers = field.getModifiers();
+ if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+ && field.getType().equals(int.class) && field.getName().startsWith(prefix)) {
+ try {
+ final int value = field.getInt(null);
+ if ((flags & value) != 0) {
+ flags &= ~value;
+ res.append(field.getName().substring(prefix.length())).append('|');
+ }
+ } catch (IllegalAccessException ignored) {
+ }
+ }
+ }
+ if (flags != 0 || res.length() == 0) {
+ res.append(Integer.toHexString(flags));
+ } else {
+ res.deleteCharAt(res.length() - 1);
+ }
+ return res.toString();
+ }
}
diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java
index 36d5b50..8b57d3d 100644
--- a/core/java/android/util/MathUtils.java
+++ b/core/java/android/util/MathUtils.java
@@ -20,7 +20,7 @@ import java.util.Random;
/**
* A class that contains utility methods related to numbers.
- *
+ *
* @hide Pending API council approval
*/
public final class MathUtils {
@@ -32,7 +32,7 @@ public final class MathUtils {
}
public static float abs(float v) {
- return v > 0 ? v : -v;
+ return v > 0 ? v : -v;
}
public static int constrain(int amount, int low, int high) {
@@ -116,6 +116,14 @@ public final class MathUtils {
return v * v;
}
+ public static float dot(float v1x, float v1y, float v2x, float v2y) {
+ return v1x * v2x + v1y * v2y;
+ }
+
+ public static float cross(float v1x, float v1y, float v2x, float v2y) {
+ return v1x * v2y - v1y * v2x;
+ }
+
public static float radians(float degrees) {
return degrees * DEG_TO_RAD;
}
@@ -142,16 +150,16 @@ public final class MathUtils {
public static float tan(float angle) {
return (float) Math.tan(angle);
- }
+ }
public static float lerp(float start, float stop, float amount) {
return start + (stop - start) * amount;
}
-
+
public static float norm(float start, float stop, float value) {
return (value - start) / (stop - start);
}
-
+
public static float map(float minStart, float minStop, float maxStart, float maxStop, float value) {
return maxStart + (maxStart - maxStop) * ((value - minStart) / (minStop - minStart));
}
@@ -164,7 +172,7 @@ public final class MathUtils {
if (howsmall >= howbig) return howsmall;
return (int) (sRandom.nextFloat() * (howbig - howsmall) + howsmall);
}
-
+
public static float random(float howbig) {
return sRandom.nextFloat() * howbig;
}
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index c8149d9..79a8489 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -71,7 +71,12 @@ import java.io.PrintWriter;
*/
public final class Choreographer {
private static final String TAG = "Choreographer";
- private static final boolean DEBUG = false;
+
+ // Prints debug messages about jank which was detected (low volume).
+ private static final boolean DEBUG_JANK = false;
+
+ // Prints debug messages about every frame and callback registered (high volume).
+ private static final boolean DEBUG_FRAMES = false;
// The default amount of time in ms between animation frames.
// When vsync is not enabled, we want to have some idea of how long we should
@@ -139,6 +144,7 @@ public final class Choreographer {
private boolean mCallbacksRunning;
private long mLastFrameTimeNanos;
private long mFrameIntervalNanos;
+ private boolean mDebugPrintNextFrameTimeDelta;
/**
* Contains information about the current frame for jank-tracking,
@@ -166,13 +172,25 @@ public final class Choreographer {
public static final int CALLBACK_ANIMATION = 1;
/**
- * Callback type: Traversal callback. Handles layout and draw. Runs last
+ * Callback type: Traversal callback. Handles layout and draw. Runs
* after all other asynchronous messages have been handled.
* @hide
*/
public static final int CALLBACK_TRAVERSAL = 2;
- private static final int CALLBACK_LAST = CALLBACK_TRAVERSAL;
+ /**
+ * Callback type: Commit callback. Handles post-draw operations for the frame.
+ * Runs after traversal completes. The {@link #getFrameTime() frame time} reported
+ * during this callback may be updated to reflect delays that occurred while
+ * traversals were in progress in case heavy layout operations caused some frames
+ * to be skipped. The frame time reported during this callback provides a better
+ * estimate of the start time of the frame in which animations (and other updates
+ * to the view hierarchy state) actually took effect.
+ * @hide
+ */
+ public static final int CALLBACK_COMMIT = 3;
+
+ private static final int CALLBACK_LAST = CALLBACK_COMMIT;
private Choreographer(Looper looper) {
mLooper = looper;
@@ -332,7 +350,7 @@ public final class Choreographer {
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
- if (DEBUG) {
+ if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
@@ -376,7 +394,7 @@ public final class Choreographer {
}
private void removeCallbacksInternal(int callbackType, Object action, Object token) {
- if (DEBUG) {
+ if (DEBUG_FRAMES) {
Log.d(TAG, "RemoveCallbacks: type=" + callbackType
+ ", action=" + action + ", token=" + token);
}
@@ -492,7 +510,7 @@ public final class Choreographer {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
- if (DEBUG) {
+ if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
@@ -509,7 +527,7 @@ public final class Choreographer {
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
- if (DEBUG) {
+ if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
@@ -526,6 +544,12 @@ public final class Choreographer {
return; // no work to do
}
+ if (DEBUG_JANK && mDebugPrintNextFrameTimeDelta) {
+ mDebugPrintNextFrameTimeDelta = false;
+ Log.d(TAG, "Frame time delta: "
+ + ((frameTimeNanos - mLastFrameTimeNanos) * 0.000001f) + " ms");
+ }
+
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
@@ -536,7 +560,7 @@ public final class Choreographer {
+ "The application may be doing too much work on its main thread.");
}
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
- if (DEBUG) {
+ if (DEBUG_JANK) {
Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001f) + " ms "
+ "which is more than the frame interval of "
+ (mFrameIntervalNanos * 0.000001f) + " ms! "
@@ -547,7 +571,7 @@ public final class Choreographer {
}
if (frameTimeNanos < mLastFrameTimeNanos) {
- if (DEBUG) {
+ if (DEBUG_JANK) {
Log.d(TAG, "Frame time appears to be going backwards. May be due to a "
+ "previously skipped frame. Waiting for next vsync.");
}
@@ -569,7 +593,9 @@ public final class Choreographer {
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
- if (DEBUG) {
+ doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
+
+ if (DEBUG_FRAMES) {
final long endNanos = System.nanoTime();
Log.d(TAG, "Frame " + frame + ": Finished, took "
+ (endNanos - startNanos) * 0.000001f + " ms, latency "
@@ -583,16 +609,43 @@ public final class Choreographer {
// We use "now" to determine when callbacks become due because it's possible
// for earlier processing phases in a frame to post callbacks that should run
// in a following phase, such as an input event that causes an animation to start.
- final long now = SystemClock.uptimeMillis();
- callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now);
+ final long now = System.nanoTime();
+ callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
+ now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
mCallbacksRunning = true;
+
+ // Update the frame time if necessary when committing the frame.
+ // We only update the frame time if we are more than 2 frames late reaching
+ // the commit phase. This ensures that the frame time which is observed by the
+ // callbacks will always increase from one frame to the next and never repeat.
+ // We never want the next frame's starting frame time to end up being less than
+ // or equal to the previous frame's commit frame time. Keep in mind that the
+ // next frame has most likely already been scheduled by now so we play it
+ // safe by ensuring the commit time is always at least one frame behind.
+ if (callbackType == Choreographer.CALLBACK_COMMIT) {
+ final long jitterNanos = now - frameTimeNanos;
+ if (jitterNanos >= 2 * mFrameIntervalNanos) {
+ final long lastFrameOffset = jitterNanos % mFrameIntervalNanos
+ + mFrameIntervalNanos;
+ if (DEBUG_JANK) {
+ Log.d(TAG, "Commit callback delayed by " + (jitterNanos * 0.000001f)
+ + " ms which is more than twice the frame interval of "
+ + (mFrameIntervalNanos * 0.000001f) + " ms! "
+ + "Setting frame time to " + (lastFrameOffset * 0.000001f)
+ + " ms in the past.");
+ mDebugPrintNextFrameTimeDelta = true;
+ }
+ frameTimeNanos = now - lastFrameOffset;
+ mLastFrameTimeNanos = frameTimeNanos;
+ }
+ }
}
try {
for (CallbackRecord c = callbacks; c != null; c = c.next) {
- if (DEBUG) {
+ if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java
index 3caf6f0..ec8f802 100644
--- a/core/java/android/view/DisplayListCanvas.java
+++ b/core/java/android/view/DisplayListCanvas.java
@@ -48,7 +48,6 @@ public class DisplayListCanvas extends Canvas {
private int mWidth;
private int mHeight;
-
static DisplayListCanvas obtain(@NonNull RenderNode node) {
if (node == null) throw new IllegalArgumentException("node cannot be null");
DisplayListCanvas canvas = sPool.acquire();
diff --git a/core/java/android/view/IGraphicsStats.aidl b/core/java/android/view/IGraphicsStats.aidl
new file mode 100644
index 0000000..c235eb2
--- /dev/null
+++ b/core/java/android/view/IGraphicsStats.aidl
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * @hide
+ */
+interface IGraphicsStats {
+ ParcelFileDescriptor requestBufferForProcess(String packageName, IBinder token);
+}
diff --git a/core/java/android/view/PhoneWindow.java b/core/java/android/view/PhoneWindow.java
index cb32697..38f4d1c 100644
--- a/core/java/android/view/PhoneWindow.java
+++ b/core/java/android/view/PhoneWindow.java
@@ -29,6 +29,7 @@ import android.os.UserHandle;
import com.android.internal.R;
import com.android.internal.util.ScreenShapeHelper;
+import com.android.internal.view.FloatingActionMode;
import com.android.internal.view.RootViewSurfaceTaker;
import com.android.internal.view.StandaloneActionMode;
import com.android.internal.view.menu.ContextMenuBuilder;
@@ -41,6 +42,7 @@ import com.android.internal.view.menu.MenuView;
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.BackgroundFallback;
import com.android.internal.widget.DecorContentParent;
+import com.android.internal.widget.FloatingToolbar;
import com.android.internal.widget.SwipeDismissLayout;
import android.app.ActivityManager;
@@ -2179,6 +2181,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
private ActionBarContextView mPrimaryActionModeView;
private PopupWindow mPrimaryActionModePopup;
private Runnable mShowPrimaryActionModePopup;
+ private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
+ private View mFloatingActionModeOriginatingView;
+ private FloatingToolbar mFloatingToolbar;
// View added at runtime to draw under the status bar area
private View mStatusGuard;
@@ -2703,18 +2708,18 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
if (mode.getType() == ActionMode.TYPE_PRIMARY) {
cleanupPrimaryActionMode();
mPrimaryActionMode = mode;
- } else {
+ } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
+ if (mFloatingActionMode != null) {
+ mFloatingActionMode.finish();
+ }
mFloatingActionMode = mode;
}
} else {
- if (type == ActionMode.TYPE_PRIMARY) {
- cleanupPrimaryActionMode();
- mode = createStandaloneActionMode(wrappedCallback);
- if (mode != null && callback.onCreateActionMode(mode, mode.getMenu())) {
- setHandledPrimaryActionMode(mode);
- } else {
- mode = null;
- }
+ mode = createActionMode(type, wrappedCallback, originatingView);
+ if (mode != null && wrappedCallback.onCreateActionMode(mode, mode.getMenu())) {
+ setHandledActionMode(mode);
+ } else {
+ mode = null;
}
}
if (mode != null && getCallback() != null && !isDestroyed()) {
@@ -2737,6 +2742,21 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
}
+ private void cleanupFloatingActionModeViews() {
+ if (mFloatingToolbar != null) {
+ mFloatingToolbar.dismiss();
+ mFloatingToolbar = null;
+ }
+ if (mFloatingActionModeOriginatingView != null) {
+ if (mFloatingToolbarPreDrawListener != null) {
+ mFloatingActionModeOriginatingView.getViewTreeObserver()
+ .removeOnPreDrawListener(mFloatingToolbarPreDrawListener);
+ mFloatingToolbarPreDrawListener = null;
+ }
+ mFloatingActionModeOriginatingView = null;
+ }
+ }
+
public void startChanging() {
mChanging = true;
}
@@ -3128,6 +3148,14 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
if (cb != null && !isDestroyed() && mFeatureId < 0) {
cb.onWindowFocusChanged(hasWindowFocus);
}
+
+ if (mFloatingToolbar != null) {
+ if (hasWindowFocus) {
+ mFloatingToolbar.show();
+ } else {
+ mFloatingToolbar.dismiss();
+ }
+ }
}
void updateWindowResizeState() {
@@ -3179,6 +3207,10 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
mPrimaryActionModePopup = null;
}
+ if (mFloatingToolbar != null) {
+ mFloatingToolbar.dismiss();
+ mFloatingToolbar = null;
+ }
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (st != null && st.menu != null && mFeatureId < 0) {
@@ -3220,7 +3252,27 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
updateColorViewTranslations();
}
+ private ActionMode createActionMode(
+ int type, ActionMode.Callback2 callback, View originatingView) {
+ switch (type) {
+ case ActionMode.TYPE_PRIMARY:
+ default:
+ return createStandaloneActionMode(callback);
+ case ActionMode.TYPE_FLOATING:
+ return createFloatingActionMode(originatingView, callback);
+ }
+ }
+
+ private void setHandledActionMode(ActionMode mode) {
+ if (mode.getType() == ActionMode.TYPE_PRIMARY) {
+ setHandledPrimaryActionMode(mode);
+ } else if (mode.getType() == ActionMode.TYPE_FLOATING) {
+ setHandledFloatingActionMode(mode);
+ }
+ }
+
private ActionMode createStandaloneActionMode(ActionMode.Callback callback) {
+ cleanupPrimaryActionMode();
if (mPrimaryActionModeView == null) {
if (isFloating()) {
// Use the action bar theme.
@@ -3291,6 +3343,35 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
+ private ActionMode createFloatingActionMode(
+ View originatingView, ActionMode.Callback2 callback) {
+ if (mFloatingActionMode != null) {
+ mFloatingActionMode.finish();
+ }
+ cleanupFloatingActionModeViews();
+ mFloatingToolbar = new FloatingToolbar(mContext, PhoneWindow.this);
+ final FloatingActionMode mode = new FloatingActionMode(
+ mContext, callback, originatingView, mFloatingToolbar);
+ mFloatingActionModeOriginatingView = originatingView;
+ mFloatingToolbarPreDrawListener =
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mode.updateViewLocationInWindow();
+ return true;
+ }
+ };
+ return mode;
+ }
+
+ private void setHandledFloatingActionMode(ActionMode mode) {
+ mFloatingActionMode = mode;
+ mFloatingActionMode.invalidate();
+ mFloatingToolbar.show();
+ mFloatingActionModeOriginatingView.getViewTreeObserver()
+ .addOnPreDrawListener(mFloatingToolbarPreDrawListener);
+ }
+
/**
* Clears out internal references when the action mode is destroyed.
*/
@@ -3328,6 +3409,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
mPrimaryActionMode = null;
} else if (mode == mFloatingActionMode) {
+ cleanupFloatingActionModeViews();
mFloatingActionMode = null;
}
if (getCallback() != null && !isDestroyed()) {
@@ -3339,6 +3421,15 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
requestFitSystemWindows();
}
+
+ @Override
+ public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+ if (mWrapped instanceof ActionMode.Callback2) {
+ ((ActionMode.Callback2) mWrapped).onGetContentRect(mode, view, outRect);
+ } else {
+ super.onGetContentRect(mode, view, outRect);
+ }
+ }
}
}
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index ef98bbc..236cfef 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -240,12 +240,7 @@ public class RenderNode {
* @see #start(int, int)
* @see #isValid()
*/
- public void end(DisplayListCanvas endCanvas) {
- if (!(endCanvas instanceof DisplayListCanvas)) {
- throw new IllegalArgumentException("Passed an invalid canvas to end!");
- }
-
- DisplayListCanvas canvas = (DisplayListCanvas) endCanvas;
+ public void end(DisplayListCanvas canvas) {
canvas.onPostDraw();
long renderNodeData = canvas.finishRecording();
nSetDisplayListData(mNativeRenderNode, renderNodeData);
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 6508cca..5cf2c5c 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -130,6 +130,7 @@ public class ScaleGestureDetector {
private float mFocusY;
private boolean mQuickScaleEnabled;
+ private boolean mButtonScaleEnabled;
private float mCurrSpan;
private float mPrevSpan;
@@ -151,14 +152,17 @@ public class ScaleGestureDetector {
private int mTouchHistoryDirection;
private long mTouchHistoryLastAcceptedTime;
private int mTouchMinMajor;
- private MotionEvent mDoubleTapEvent;
- private int mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
private final Handler mHandler;
+ private float mAnchoredScaleStartX;
+ private float mAnchoredScaleStartY;
+ private int mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+
private static final long TOUCH_STABILIZE_TIME = 128; // ms
- private static final int DOUBLE_TAP_MODE_NONE = 0;
- private static final int DOUBLE_TAP_MODE_IN_PROGRESS = 1;
private static final float SCALE_FACTOR = .5f;
+ private static final int ANCHORED_SCALE_MODE_NONE = 0;
+ private static final int ANCHORED_SCALE_MODE_DOUBLE_TAP = 1;
+ private static final int ANCHORED_SCALE_MODE_BUTTON = 2;
/**
@@ -310,8 +314,17 @@ public class ScaleGestureDetector {
mGestureDetector.onTouchEvent(event);
}
+ final int count = event.getPointerCount();
+ final int toolType = event.getToolType(0);
+ final boolean isButtonTool = toolType == MotionEvent.TOOL_TYPE_STYLUS
+ || toolType == MotionEvent.TOOL_TYPE_MOUSE;
+ final boolean isAnchoredScaleButtonDown = isButtonTool && (count == 1)
+ && (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0;
+
+ final boolean anchoredScaleCancelled =
+ mAnchoredScaleMode == ANCHORED_SCALE_MODE_BUTTON && !isAnchoredScaleButtonDown;
final boolean streamComplete = action == MotionEvent.ACTION_UP ||
- action == MotionEvent.ACTION_CANCEL;
+ action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
if (action == MotionEvent.ACTION_DOWN || streamComplete) {
// Reset any scale in progress with the listener.
@@ -321,11 +334,11 @@ public class ScaleGestureDetector {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = 0;
- mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
- } else if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS && streamComplete) {
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+ } else if (inAnchoredScaleMode() && streamComplete) {
mInProgress = false;
mInitialSpan = 0;
- mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
}
if (streamComplete) {
@@ -334,25 +347,32 @@ public class ScaleGestureDetector {
}
}
+ if (!mInProgress && mButtonScaleEnabled && !inAnchoredScaleMode()
+ && !streamComplete && isAnchoredScaleButtonDown) {
+ // Start of a button scale gesture
+ mAnchoredScaleStartX = event.getX();
+ mAnchoredScaleStartY = event.getY();
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_BUTTON;
+ mInitialSpan = 0;
+ }
+
final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_POINTER_UP ||
- action == MotionEvent.ACTION_POINTER_DOWN;
-
+ action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;
final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? event.getActionIndex() : -1;
// Determine focal point
float sumX = 0, sumY = 0;
- final int count = event.getPointerCount();
final int div = pointerUp ? count - 1 : count;
final float focusX;
final float focusY;
- if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS) {
- // In double tap mode, the focal pt is always where the double tap
- // gesture started
- focusX = mDoubleTapEvent.getX();
- focusY = mDoubleTapEvent.getY();
+ if (inAnchoredScaleMode()) {
+ // In anchored scale mode, the focal pt is always where the double tap
+ // or button down gesture started
+ focusX = mAnchoredScaleStartX;
+ focusY = mAnchoredScaleStartY;
if (event.getY() < focusY) {
mEventBeforeOrAboveStartingGestureEvent = true;
} else {
@@ -390,7 +410,7 @@ public class ScaleGestureDetector {
final float spanX = devX * 2;
final float spanY = devY * 2;
final float span;
- if (inDoubleTapMode()) {
+ if (inAnchoredScaleMode()) {
span = spanY;
} else {
span = (float) Math.hypot(spanX, spanY);
@@ -402,11 +422,10 @@ public class ScaleGestureDetector {
final boolean wasInProgress = mInProgress;
mFocusX = focusX;
mFocusY = focusY;
- if (!inDoubleTapMode() && mInProgress && (span < mMinSpan || configChanged)) {
+ if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {
mListener.onScaleEnd(this);
mInProgress = false;
mInitialSpan = span;
- mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
}
if (configChanged) {
mPrevSpanX = mCurrSpanX = spanX;
@@ -414,7 +433,7 @@ public class ScaleGestureDetector {
mInitialSpan = mPrevSpan = mCurrSpan = span;
}
- final int minSpan = inDoubleTapMode() ? mSpanSlop : mMinSpan;
+ final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
if (!mInProgress && span >= minSpan &&
(wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
mPrevSpanX = mCurrSpanX = spanX;
@@ -447,9 +466,8 @@ public class ScaleGestureDetector {
return true;
}
-
- private boolean inDoubleTapMode() {
- return mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS;
+ private boolean inAnchoredScaleMode() {
+ return mAnchoredScaleMode != ANCHORED_SCALE_MODE_NONE;
}
/**
@@ -466,8 +484,9 @@ public class ScaleGestureDetector {
@Override
public boolean onDoubleTap(MotionEvent e) {
// Double tap: start watching for a swipe
- mDoubleTapEvent = e;
- mDoubleTapMode = DOUBLE_TAP_MODE_IN_PROGRESS;
+ mAnchoredScaleStartX = e.getX();
+ mAnchoredScaleStartY = e.getY();
+ mAnchoredScaleMode = ANCHORED_SCALE_MODE_DOUBLE_TAP;
return true;
}
};
@@ -484,6 +503,27 @@ public class ScaleGestureDetector {
}
/**
+ * Sets whether the associates {@link OnScaleGestureListener} should receive onScale callbacks
+ * when the user presses a {@value MotionEvent#BUTTON_SECONDARY} (right mouse button, stylus
+ * first button) and drags the pointer on the screen. Note that this is enabled by default if
+ * the app targets API 23 and newer.
+ *
+ * @param scales true to enable stylus or mouse scaling, false to disable.
+ */
+ public void setSecondaryButtonScaleEnabled(boolean scales) {
+ mButtonScaleEnabled = scales;
+ }
+
+ /**
+ * Return whether the button scale gesture, in which the user presses a
+ * {@value MotionEvent#BUTTON_SECONDARY} (right mouse button, stylus first button) and drags the
+ * pointer on the screen, should perform scaling. {@see #setButtonScaleEnabled(boolean)}.
+ */
+ public boolean isSecondaryButtonScaleEnabled() {
+ return mButtonScaleEnabled;
+ }
+
+ /**
* Returns {@code true} if a scale gesture is in progress.
*/
public boolean isInProgress() {
@@ -586,7 +626,7 @@ public class ScaleGestureDetector {
* @return The current scaling factor.
*/
public float getScaleFactor() {
- if (inDoubleTapMode()) {
+ if (inAnchoredScaleMode()) {
// Drag is moving up; the further away from the gesture
// start, the smaller the span should be, the closer,
// the larger the span, and therefore the larger the scale
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 031be07..87d5d9a 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -23,7 +23,9 @@ import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Binder;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Trace;
@@ -124,7 +126,7 @@ public class ThreadedRenderer extends HardwareRenderer {
mRootNode.setClipToBounds(false);
mNativeProxy = nCreateProxy(translucent, rootNodePtr);
- AtlasInitializer.sInstance.init(context, mNativeProxy);
+ ProcessInitializer.sInstance.init(context, mNativeProxy);
loadSystemProperties();
}
@@ -410,15 +412,44 @@ public class ThreadedRenderer extends HardwareRenderer {
nTrimMemory(level);
}
- private static class AtlasInitializer {
- static AtlasInitializer sInstance = new AtlasInitializer();
+ public static void dumpProfileData(byte[] data, FileDescriptor fd) {
+ nDumpProfileData(data, fd);
+ }
+
+ private static class ProcessInitializer {
+ static ProcessInitializer sInstance = new ProcessInitializer();
+ static IGraphicsStats sGraphicsStatsService;
+ private static IBinder sProcToken;
private boolean mInitialized = false;
- private AtlasInitializer() {}
+ private ProcessInitializer() {}
synchronized void init(Context context, long renderProxy) {
if (mInitialized) return;
+ mInitialized = true;
+ initGraphicsStats(context, renderProxy);
+ initAssetAtlas(context, renderProxy);
+ }
+
+ private static void initGraphicsStats(Context context, long renderProxy) {
+ IBinder binder = ServiceManager.getService("graphicsstats");
+ if (binder == null) return;
+
+ sGraphicsStatsService = IGraphicsStats.Stub.asInterface(binder);
+ sProcToken = new Binder();
+ try {
+ final String pkg = context.getApplicationInfo().packageName;
+ ParcelFileDescriptor pfd = sGraphicsStatsService.
+ requestBufferForProcess(pkg, sProcToken);
+ nSetProcessStatsBuffer(renderProxy, pfd.getFd());
+ pfd.close();
+ } catch (Exception e) {
+ Log.w(LOG_TAG, "Could not acquire gfx stats buffer", e);
+ }
+ }
+
+ private static void initAssetAtlas(Context context, long renderProxy) {
IBinder binder = ServiceManager.getService("assetatlas");
if (binder == null) return;
@@ -432,7 +463,6 @@ public class ThreadedRenderer extends HardwareRenderer {
// TODO Remove after fixing b/15425820
validateMap(context, map);
nSetAtlas(renderProxy, buffer, map);
- mInitialized = true;
}
// If IAssetAtlas is not the same class as the IBinder
// we are using a remote service and we can safely
@@ -477,6 +507,7 @@ public class ThreadedRenderer extends HardwareRenderer {
static native void setupShadersDiskCache(String cacheFile);
private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map);
+ private static native void nSetProcessStatsBuffer(long nativeProxy, int fd);
private static native long nCreateRootRenderNode();
private static native long nCreateProxy(boolean translucent, long rootRenderNode);
@@ -514,4 +545,5 @@ public class ThreadedRenderer extends HardwareRenderer {
private static native void nDumpProfileInfo(long nativeProxy, FileDescriptor fd,
@DumpFlags int dumpFlags);
+ private static native void nDumpProfileData(byte[] data, FileDescriptor fd);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index db8109f..b6f1e3b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -91,6 +91,7 @@ import android.view.animation.Transformation;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.widget.Checkable;
import android.widget.ScrollBarDrawable;
import static android.os.Build.VERSION_CODES.*;
@@ -3196,9 +3197,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static class ForegroundInfo {
private Drawable mDrawable;
private TintInfo mTintInfo;
- private int mGravity = Gravity.START | Gravity.TOP;
+ private int mGravity = Gravity.FILL;
private boolean mInsidePadding = true;
- private boolean mBoundsChanged;
+ private boolean mBoundsChanged = true;
private final Rect mSelfBounds = new Rect();
private final Rect mOverlayBounds = new Rect();
}
@@ -3582,6 +3583,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
// of whether a layout was requested on that View.
sIgnoreMeasureCache = targetSdkVersion < KITKAT;
+ Canvas.sCompatibilityRestore = targetSdkVersion < MNC;
+
sCompatibilityDone = true;
}
}
@@ -5676,10 +5679,143 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Called when assist structure is being retrieved from a view as part of
* {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
- * @param structure Additional standard structured view structure to supply.
- * @param extras Non-standard extensions.
+ * @param structure Fill in with structured view data. The default implementation
+ * fills in all data that can be inferred from the view itself.
+ */
+ public void onProvideAssistStructure(ViewAssistStructure structure) {
+ final int id = mID;
+ if (id > 0 && (id&0xff000000) != 0 && (id&0x00ff0000) != 0
+ && (id&0x0000ffff) != 0) {
+ String pkg, type, entry;
+ try {
+ final Resources res = getResources();
+ entry = res.getResourceEntryName(id);
+ type = res.getResourceTypeName(id);
+ pkg = res.getResourcePackageName(id);
+ } catch (Resources.NotFoundException e) {
+ entry = type = pkg = null;
+ }
+ structure.setId(id, pkg, type, entry);
+ } else {
+ structure.setId(id, null, null, null);
+ }
+ structure.setDimens(mLeft, mTop, mScrollX, mScrollY, mRight-mLeft, mBottom-mTop);
+ structure.setVisibility(getVisibility());
+ structure.setEnabled(isEnabled());
+ if (isClickable()) {
+ structure.setClickable(true);
+ }
+ if (isFocusable()) {
+ structure.setFocusable(true);
+ }
+ if (isFocused()) {
+ structure.setFocused(true);
+ }
+ if (isAccessibilityFocused()) {
+ structure.setAccessibilityFocused(true);
+ }
+ if (isSelected()) {
+ structure.setSelected(true);
+ }
+ if (isActivated()) {
+ structure.setActivated(true);
+ }
+ if (isLongClickable()) {
+ structure.setLongClickable(true);
+ }
+ if (this instanceof Checkable) {
+ structure.setCheckable(true);
+ if (((Checkable)this).isChecked()) {
+ structure.setChecked(true);
+ }
+ }
+ structure.setClassName(getAccessibilityClassName().toString());
+ structure.setContentDescription(getContentDescription());
+ }
+
+ /**
+ * Called when assist structure is being retrieved from a view as part of
+ * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData} to
+ * generate additional virtual structure under this view. The defaullt implementation
+ * uses {@link #getAccessibilityNodeProvider()} to try to generate this from the
+ * view's virtual accessibility nodes, if any. You can override this for a more
+ * optimal implementation providing this data.
+ */
+ public void onProvideVirtualAssistStructure(ViewAssistStructure structure) {
+ AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
+ if (provider != null) {
+ AccessibilityNodeInfo info = createAccessibilityNodeInfo();
+ Log.i("View", "Provider of " + this + ": children=" + info.getChildCount());
+ structure.setChildCount(1);
+ ViewAssistStructure root = structure.newChild(0);
+ populateVirtualAssistStructure(root, provider, info);
+ info.recycle();
+ }
+ }
+
+ private void populateVirtualAssistStructure(ViewAssistStructure structure,
+ AccessibilityNodeProvider provider, AccessibilityNodeInfo info) {
+ structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
+ null, null, null);
+ Rect rect = structure.getTempRect();
+ info.getBoundsInParent(rect);
+ structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
+ structure.setVisibility(VISIBLE);
+ structure.setEnabled(info.isEnabled());
+ if (info.isClickable()) {
+ structure.setClickable(true);
+ }
+ if (info.isFocusable()) {
+ structure.setFocusable(true);
+ }
+ if (info.isFocused()) {
+ structure.setFocused(true);
+ }
+ if (info.isAccessibilityFocused()) {
+ structure.setAccessibilityFocused(true);
+ }
+ if (info.isSelected()) {
+ structure.setSelected(true);
+ }
+ if (info.isLongClickable()) {
+ structure.setLongClickable(true);
+ }
+ if (info.isCheckable()) {
+ structure.setCheckable(true);
+ if (info.isChecked()) {
+ structure.setChecked(true);
+ }
+ }
+ CharSequence cname = info.getClassName();
+ structure.setClassName(cname != null ? cname.toString() : null);
+ structure.setContentDescription(info.getContentDescription());
+ Log.i("View", "vassist " + cname + " @ " + rect.toShortString()
+ + " text=" + info.getText() + " cd=" + info.getContentDescription());
+ if (info.getText() != null || info.getError() != null) {
+ structure.setText(info.getText(), info.getTextSelectionStart(),
+ info.getTextSelectionEnd());
+ }
+ final int NCHILDREN = info.getChildCount();
+ if (NCHILDREN > 0) {
+ structure.setChildCount(NCHILDREN);
+ for (int i=0; i<NCHILDREN; i++) {
+ AccessibilityNodeInfo cinfo = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
+ ViewAssistStructure child = structure.newChild(i);
+ populateVirtualAssistStructure(child, provider, cinfo);
+ cinfo.recycle();
+ }
+ }
+ }
+
+ /**
+ * Dispatch creation of {@link ViewAssistStructure} down the hierarchy. The default
+ * implementation calls {@link #onProvideAssistStructure} and
+ * {@link #onProvideVirtualAssistStructure}.
*/
- public void onProvideAssistStructure(ViewAssistStructure structure, Bundle extras) {
+ public void dispatchProvideAssistStructure(ViewAssistStructure structure) {
+ onProvideAssistStructure(structure);
+ onProvideVirtualAssistStructure(structure);
}
/**
@@ -8264,7 +8400,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return true;
}
} break;
- case R.id.accessibility_action_show_on_screen: {
+ case R.id.accessibilityActionShowOnScreen: {
if (mAttachInfo != null) {
final Rect r = mAttachInfo.mTmpInvalRect;
getDrawingRect(r);
@@ -9959,6 +10095,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param oldt Previous vertical scroll origin.
*/
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ notifySubtreeAccessibilityStateChangedIfNeeded();
+
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
postSendViewScrolledAccessibilityEventCallback();
}
@@ -11162,6 +11300,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
+ notifySubtreeAccessibilityStateChangedIfNeeded();
}
}
@@ -16674,6 +16813,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
mForegroundInfo.mDrawable = foreground;
+ mForegroundInfo.mBoundsChanged = true;
if (foreground != null) {
setWillNotDraw(false);
foreground.setCallback(this);
@@ -16778,7 +16918,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@Nullable
public ColorStateList getForegroundTintList() {
return mForegroundInfo != null && mForegroundInfo.mTintInfo != null
- ? mBackgroundTint.mTintList : null;
+ ? mForegroundInfo.mTintInfo.mTintList : null;
}
/**
diff --git a/core/java/android/view/ViewAssistStructure.java b/core/java/android/view/ViewAssistStructure.java
index 5132bb9..7d263c5 100644
--- a/core/java/android/view/ViewAssistStructure.java
+++ b/core/java/android/view/ViewAssistStructure.java
@@ -16,6 +16,8 @@
package android.view;
+import android.graphics.Rect;
+import android.os.Bundle;
import android.text.TextPaint;
/**
@@ -23,6 +25,37 @@ import android.text.TextPaint;
* View.onProvideAssistStructure}.
*/
public abstract class ViewAssistStructure {
+ public abstract void setId(int id, String packageName, String typeName, String entryName);
+
+ public abstract void setDimens(int left, int top, int scrollX, int scrollY, int width,
+ int height);
+
+ public abstract void setVisibility(int visibility);
+
+ public abstract void setEnabled(boolean state);
+
+ public abstract void setClickable(boolean state);
+
+ public abstract void setLongClickable(boolean state);
+
+ public abstract void setFocusable(boolean state);
+
+ public abstract void setFocused(boolean state);
+
+ public abstract void setAccessibilityFocused(boolean state);
+
+ public abstract void setCheckable(boolean state);
+
+ public abstract void setChecked(boolean state);
+
+ public abstract void setSelected(boolean state);
+
+ public abstract void setActivated(boolean state);
+
+ public abstract void setClassName(String className);
+
+ public abstract void setContentDescription(CharSequence contentDescription);
+
public abstract void setText(CharSequence text);
public abstract void setText(CharSequence text, int selectionStart, int selectionEnd);
public abstract void setTextPaint(TextPaint paint);
@@ -32,4 +65,17 @@ public abstract class ViewAssistStructure {
public abstract int getTextSelectionStart();
public abstract int getTextSelectionEnd();
public abstract CharSequence getHint();
+
+ public abstract Bundle editExtras();
+ public abstract void clearExtras();
+
+ public abstract void setChildCount(int num);
+ public abstract int getChildCount();
+ public abstract ViewAssistStructure newChild(int index);
+
+ public abstract ViewAssistStructure asyncNewChild(int index);
+ public abstract void asyncCommit();
+
+ /** @hide */
+ public abstract Rect getTempRect();
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 50e64c6..a237afd 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -1005,31 +1005,23 @@ public class ViewDebug {
return fields;
}
- final ArrayList<Field> declaredFields = new ArrayList();
- klass.getDeclaredFieldsUnchecked(false, declaredFields);
-
- final ArrayList<Field> foundFields = new ArrayList<Field>();
- final int count = declaredFields.size();
- for (int i = 0; i < count; i++) {
- final Field field = declaredFields.get(i);
-
- // Ensure the field type can be resolved.
- try {
- field.getType();
- } catch (NoClassDefFoundError e) {
- continue;
- }
-
- if (field.isAnnotationPresent(ExportedProperty.class)) {
- field.setAccessible(true);
- foundFields.add(field);
- sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
+ try {
+ final Field[] declaredFields = klass.getDeclaredFieldsUnchecked(false);
+ final ArrayList<Field> foundFields = new ArrayList<Field>();
+ for (final Field field : declaredFields) {
+ // Fields which can't be resolved have a null type.
+ if (field.getType() != null && field.isAnnotationPresent(ExportedProperty.class)) {
+ field.setAccessible(true);
+ foundFields.add(field);
+ sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
+ }
}
+ fields = foundFields.toArray(new Field[foundFields.size()]);
+ map.put(klass, fields);
+ } catch (NoClassDefFoundError e) {
+ throw new AssertionError(e);
}
- fields = foundFields.toArray(new Field[foundFields.size()]);
- map.put(klass, fields);
-
return fields;
}
@@ -1651,4 +1643,4 @@ public class ViewDebug {
}
});
}
-} \ No newline at end of file
+}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 87f3e94..d0705bb 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2852,6 +2852,33 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return false;
}
+ /**
+ * Dispatch creation of {@link ViewAssistStructure} down the hierarchy. This implementation
+ * adds in all child views of the view group, in addition to calling the default View
+ * implementation.
+ */
+ public void dispatchProvideAssistStructure(ViewAssistStructure structure) {
+ super.dispatchProvideAssistStructure(structure);
+ if (structure.getChildCount() == 0) {
+ final int childrenCount = getChildCount();
+ if (childrenCount > 0) {
+ structure.setChildCount(childrenCount);
+ final ArrayList<View> preorderedList = buildOrderedChildList();
+ final boolean customOrder = preorderedList == null
+ && isChildrenDrawingOrderEnabled();
+ final View[] children = mChildren;
+ for (int i=0; i<childrenCount; i++) {
+ final int childIndex = customOrder
+ ? getChildDrawingOrder(childrenCount, i) : i;
+ final View child = (preorderedList == null)
+ ? children[childIndex] : preorderedList.get(childIndex);
+ ViewAssistStructure cstructure = structure.newChild(i);
+ child.dispatchProvideAssistStructure(cstructure);
+ }
+ }
+ }
+ }
+
/** @hide */
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
@@ -5727,12 +5754,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
- resultSize = 0;
+ resultSize = size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
- resultSize = 0;
+ resultSize = size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 294174a..4158340 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6268,41 +6268,79 @@ public final class ViewRootImpl implements ViewParent,
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
- if (mAccessibilityFocusedHost != null && mAccessibilityFocusedVirtualView != null) {
- // We care only for changes rooted in the focused host.
- final long eventSourceId = event.getSourceNodeId();
- final int hostViewId = AccessibilityNodeInfo.getAccessibilityViewId(
- eventSourceId);
- if (hostViewId != mAccessibilityFocusedHost.getAccessibilityViewId()) {
- break;
- }
-
- // We only care about changes that may change the virtual focused view bounds.
- final int changes = event.getContentChangeTypes();
- if ((changes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0
- || changes == AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED) {
- AccessibilityNodeProvider provider = mAccessibilityFocusedHost
- .getAccessibilityNodeProvider();
- if (provider != null) {
- final int virtualChildId = AccessibilityNodeInfo.getVirtualDescendantId(
- mAccessibilityFocusedVirtualView.getSourceNodeId());
- if (virtualChildId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
- mAccessibilityFocusedVirtualView = provider
- .createAccessibilityNodeInfo(
- AccessibilityNodeProvider.HOST_VIEW_ID);
- } else {
- mAccessibilityFocusedVirtualView = provider
- .createAccessibilityNodeInfo(virtualChildId);
- }
- }
- }
- }
+ handleWindowContentChangedEvent(event);
} break;
}
mAccessibilityManager.sendAccessibilityEvent(event);
return true;
}
+ /**
+ * Updates the focused virtual view, when necessary, in response to a
+ * content changed event.
+ * <p>
+ * This is necessary to get updated bounds after a position change.
+ *
+ * @param event an accessibility event of type
+ * {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}
+ */
+ private void handleWindowContentChangedEvent(AccessibilityEvent event) {
+ // No virtual view focused, nothing to do here.
+ if (mAccessibilityFocusedHost == null || mAccessibilityFocusedVirtualView == null) {
+ return;
+ }
+
+ // If we have a node but no provider, abort.
+ final AccessibilityNodeProvider provider =
+ mAccessibilityFocusedHost.getAccessibilityNodeProvider();
+ if (provider == null) {
+ // TODO: Should we clear the focused virtual view?
+ return;
+ }
+
+ // We only care about change types that may affect the bounds of the
+ // focused virtual view.
+ final int changes = event.getContentChangeTypes();
+ if ((changes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) == 0
+ && changes != AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED) {
+ return;
+ }
+
+ final long eventSourceNodeId = event.getSourceNodeId();
+ final int changedViewId = AccessibilityNodeInfo.getAccessibilityViewId(eventSourceNodeId);
+
+ // Search up the tree for subtree containment.
+ boolean hostInSubtree = false;
+ View root = mAccessibilityFocusedHost;
+ while (root != null && !hostInSubtree) {
+ if (changedViewId == root.getAccessibilityViewId()) {
+ hostInSubtree = true;
+ } else {
+ final ViewParent parent = root.getParent();
+ if (parent instanceof View) {
+ root = (View) parent;
+ } else {
+ root = null;
+ }
+ }
+ }
+
+ // We care only about changes in subtrees containing the host view.
+ if (!hostInSubtree) {
+ return;
+ }
+
+ final long focusedSourceNodeId = mAccessibilityFocusedVirtualView.getSourceNodeId();
+ int focusedChildId = AccessibilityNodeInfo.getVirtualDescendantId(focusedSourceNodeId);
+ if (focusedChildId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
+ // TODO: Should we clear the focused virtual view?
+ focusedChildId = AccessibilityNodeProvider.HOST_VIEW_ID;
+ }
+
+ // Refresh the node for the focused virtual view.
+ mAccessibilityFocusedVirtualView = provider.createAccessibilityNodeInfo(focusedChildId);
+ }
+
@Override
public void notifySubtreeAccessibilityStateChanged(View child, View source, int changeType) {
postSendWindowContentChangedCallback(source, changeType);
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 9a92932..36f047e 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -1856,14 +1856,14 @@ public abstract class Window {
public abstract int getStatusBarColor();
/**
- * Sets the color of the status bar to {@param color}.
+ * Sets the color of the status bar to {@code color}.
*
* For this to take effect,
* the window must be drawing the system bar backgrounds with
* {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} and
* {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS} must not be set.
*
- * If {@param color} is not opaque, consider setting
+ * If {@code color} is not opaque, consider setting
* {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_STABLE} and
* {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}.
* <p>
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 66dae7b..54d78f3 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -223,6 +223,7 @@ public interface WindowManager extends ViewManager {
@ViewDebug.IntToString(from = TYPE_MAGNIFICATION_OVERLAY, to = "TYPE_MAGNIFICATION_OVERLAY"),
@ViewDebug.IntToString(from = TYPE_PRIVATE_PRESENTATION, to = "TYPE_PRIVATE_PRESENTATION"),
@ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION, to = "TYPE_VOICE_INTERACTION"),
+ @ViewDebug.IntToString(from = TYPE_VOICE_INTERACTION_STARTING, to = "TYPE_VOICE_INTERACTION_STARTING"),
})
public int type;
@@ -549,6 +550,12 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_ACCESSIBILITY_OVERLAY = FIRST_SYSTEM_WINDOW+32;
/**
+ * Window type: Starting window for voice interaction layer.
+ * @hide
+ */
+ public static final int TYPE_VOICE_INTERACTION_STARTING = FIRST_SYSTEM_WINDOW+33;
+
+ /**
* End of types of system windows.
*/
public static final int LAST_SYSTEM_WINDOW = 2999;
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 77082b0..0736ed8 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -266,7 +266,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* Action to set the selection. Performing this action with no arguments
* clears the selection.
* <p>
- * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_SELECTION_START_INT},
+ * <strong>Arguments:</strong>
+ * {@link #ACTION_ARGUMENT_SELECTION_START_INT},
* {@link #ACTION_ARGUMENT_SELECTION_END_INT}<br>
* <strong>Example:</strong>
* <code><pre><p>
@@ -302,7 +303,8 @@ public class AccessibilityNodeInfo implements Parcelable {
* null</code> or empty {@link CharSequence} will clear the text. This action will also put the
* cursor at the end of text.
* <p>
- * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
+ * <strong>Arguments:</strong>
+ * {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br>
* <strong>Example:</strong>
* <code><pre><p>
* Bundle arguments = new Bundle();
@@ -326,12 +328,15 @@ public class AccessibilityNodeInfo implements Parcelable {
* Argument for which movement granularity to be used when traversing the node text.
* <p>
* <strong>Type:</strong> int<br>
- * <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY},
- * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY}</li>
+ * <li>{@link AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}</li>
+ * </ul>
* </p>
*
- * @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY
- * @see #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
*/
public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT =
"ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
@@ -340,12 +345,15 @@ public class AccessibilityNodeInfo implements Parcelable {
* Argument for which HTML element to get moving to the next/previous HTML element.
* <p>
* <strong>Type:</strong> String<br>
- * <strong>Actions:</strong> {@link #ACTION_NEXT_HTML_ELEMENT},
- * {@link #ACTION_PREVIOUS_HTML_ELEMENT}
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_NEXT_HTML_ELEMENT}</li>
+ * <li>{@link AccessibilityAction#ACTION_PREVIOUS_HTML_ELEMENT}</li>
+ * </ul>
* </p>
*
- * @see #ACTION_NEXT_HTML_ELEMENT
- * @see #ACTION_PREVIOUS_HTML_ELEMENT
+ * @see AccessibilityAction#ACTION_NEXT_HTML_ELEMENT
+ * @see AccessibilityAction#ACTION_PREVIOUS_HTML_ELEMENT
*/
public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING =
"ACTION_ARGUMENT_HTML_ELEMENT_STRING";
@@ -355,12 +363,14 @@ public class AccessibilityNodeInfo implements Parcelable {
* or to move it otherwise.
* <p>
* <strong>Type:</strong> boolean<br>
- * <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY},
- * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}
- * </p>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY}</li>
+ * <li>{@link AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}</li>
+ * </ul>
*
- * @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY
- * @see #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityAction#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+ * @see AccessibilityAction#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
*/
public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN =
"ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";
@@ -369,10 +379,12 @@ public class AccessibilityNodeInfo implements Parcelable {
* Argument for specifying the selection start.
* <p>
* <strong>Type:</strong> int<br>
- * <strong>Actions:</strong> {@link #ACTION_SET_SELECTION}
- * </p>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_SELECTION}</li>
+ * </ul>
*
- * @see #ACTION_SET_SELECTION
+ * @see AccessibilityAction#ACTION_SET_SELECTION
*/
public static final String ACTION_ARGUMENT_SELECTION_START_INT =
"ACTION_ARGUMENT_SELECTION_START_INT";
@@ -381,26 +393,58 @@ public class AccessibilityNodeInfo implements Parcelable {
* Argument for specifying the selection end.
* <p>
* <strong>Type:</strong> int<br>
- * <strong>Actions:</strong> {@link #ACTION_SET_SELECTION}
- * </p>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_SELECTION}</li>
+ * </ul>
*
- * @see #ACTION_SET_SELECTION
+ * @see AccessibilityAction#ACTION_SET_SELECTION
*/
public static final String ACTION_ARGUMENT_SELECTION_END_INT =
"ACTION_ARGUMENT_SELECTION_END_INT";
/**
- * Argument for specifying the text content to set
+ * Argument for specifying the text content to set.
* <p>
* <strong>Type:</strong> CharSequence<br>
- * <strong>Actions:</strong> {@link #ACTION_SET_TEXT}
- * </p>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SET_TEXT}</li>
+ * </ul>
*
- * @see #ACTION_SET_TEXT
+ * @see AccessibilityAction#ACTION_SET_TEXT
*/
public static final String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE =
"ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
+ /**
+ * Argument for specifying the collection row to make visible on screen.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SCROLL_TO_POSITION}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SCROLL_TO_POSITION
+ */
+ public static final String ACTION_ARGUMENT_ROW_INT =
+ "android.view.accessibility.action.ARGUMENT_ROW_INT";
+
+ /**
+ * Argument for specifying the collection column to make visible on screen.
+ * <p>
+ * <strong>Type:</strong> int<br>
+ * <strong>Actions:</strong>
+ * <ul>
+ * <li>{@link AccessibilityAction#ACTION_SCROLL_TO_POSITION}</li>
+ * </ul>
+ *
+ * @see AccessibilityAction#ACTION_SCROLL_TO_POSITION
+ */
+ public static final String ACTION_ARGUMENT_COLUMN_INT =
+ "android.view.accessibility.action.ARGUMENT_COLUMN_INT";
+
// Focus types
/**
@@ -539,7 +583,7 @@ public class AccessibilityNodeInfo implements Parcelable {
// Housekeeping.
private static final int MAX_POOL_SIZE = 50;
private static final SynchronizedPool<AccessibilityNodeInfo> sPool =
- new SynchronizedPool<AccessibilityNodeInfo>(MAX_POOL_SIZE);
+ new SynchronizedPool<>(MAX_POOL_SIZE);
private boolean mSealed;
@@ -970,7 +1014,7 @@ public class AccessibilityNodeInfo implements Parcelable {
}
if (mActions == null) {
- mActions = new ArrayList<AccessibilityAction>();
+ mActions = new ArrayList<>();
}
mActions.remove(action);
@@ -1562,7 +1606,7 @@ public class AccessibilityNodeInfo implements Parcelable {
}
/**
- * Sets whether this node is visible to the user.
+ * Gets whether this node is visible to the user.
*
* @return Whether the node is visible to the user.
*/
@@ -3411,9 +3455,24 @@ public class AccessibilityNodeInfo implements Parcelable {
* @see View#requestRectangleOnScreen(Rect)
*/
public static final AccessibilityAction ACTION_SHOW_ON_SCREEN =
- new AccessibilityAction(R.id.accessibility_action_show_on_screen, null);
+ new AccessibilityAction(R.id.accessibilityActionShowOnScreen, null);
+
+ /**
+ * Action that scrolls the node to make the specified collection
+ * position visible on screen.
+ * <p>
+ * <strong>Arguments:</strong>
+ * <ul>
+ * <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_ROW_INT}</li>
+ * <li>{@link AccessibilityNodeInfo#ACTION_ARGUMENT_COLUMN_INT}</li>
+ * <ul>
+ *
+ * @see AccessibilityNodeInfo#getCollectionInfo()
+ */
+ public static final AccessibilityAction ACTION_SCROLL_TO_POSITION =
+ new AccessibilityAction(R.id.accessibilityActionScrollToPosition, null);
- private static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<AccessibilityAction>();
+ private static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<>();
static {
sStandardActions.add(ACTION_FOCUS);
sStandardActions.add(ACTION_CLEAR_FOCUS);
@@ -3438,6 +3497,7 @@ public class AccessibilityNodeInfo implements Parcelable {
sStandardActions.add(ACTION_DISMISS);
sStandardActions.add(ACTION_SET_TEXT);
sStandardActions.add(ACTION_SHOW_ON_SCREEN);
+ sStandardActions.add(ACTION_SCROLL_TO_POSITION);
}
private final int mActionId;
@@ -3658,7 +3718,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private static final int MAX_POOL_SIZE = 20;
private static final SynchronizedPool<CollectionInfo> sPool =
- new SynchronizedPool<CollectionInfo>(MAX_POOL_SIZE);
+ new SynchronizedPool<>(MAX_POOL_SIZE);
private int mRowCount;
private int mColumnCount;
@@ -3804,7 +3864,7 @@ public class AccessibilityNodeInfo implements Parcelable {
private static final int MAX_POOL_SIZE = 20;
private static final SynchronizedPool<CollectionItemInfo> sPool =
- new SynchronizedPool<CollectionItemInfo>(MAX_POOL_SIZE);
+ new SynchronizedPool<>(MAX_POOL_SIZE);
/**
* Obtains a pooled instance that is a clone of another one.
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 4737e9b..0b18bb8 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -284,13 +284,19 @@ public class WebChromeClient {
* currently set for that origin. The host application should invoke the
* specified callback with the desired permission state. See
* {@link GeolocationPermissions} for details.
+ *
+ * If this method isn't overridden, the callback is invoked with permission
+ * denied state.
+ *
* @param origin The origin of the web content attempting to use the
* Geolocation API.
* @param callback The callback to use to set the permission state for the
* origin.
*/
public void onGeolocationPermissionsShowPrompt(String origin,
- GeolocationPermissions.Callback callback) {}
+ GeolocationPermissions.Callback callback) {
+ callback.invoke(origin, false, false);
+ }
/**
* Notify the host application that a request for Geolocation permissions,
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index aa77d5e..e7c4328 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -40,6 +40,7 @@ import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewAssistStructure;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
@@ -2424,6 +2425,13 @@ public class WebView extends AbsoluteLayout
return WebView.class.getName();
}
+ @Override
+ public void onProvideVirtualAssistStructure(ViewAssistStructure structure) {
+ super.onProvideVirtualAssistStructure(structure);
+ // TODO: enable when chromium backend lands.
+ // mProvider.getViewDelegate().onProvideVirtualAssistStructure(structure);
+ }
+
/** @hide */
@Override
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index 53c7e04..8a2b3fa 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -200,8 +200,6 @@ public class WebViewClient {
public static final int ERROR_FILE_NOT_FOUND = -14;
/** Too many requests during this load */
public static final int ERROR_TOO_MANY_REQUESTS = -15;
- /** Request blocked by the browser */
- public static final int ERROR_BLOCKED = -16;
/**
* Report an error to the host application. These errors are unrecoverable
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index fa2ce1b..d5787de 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -32,6 +32,7 @@ import android.print.PrintDocumentAdapter;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewAssistStructure;
import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -298,6 +299,8 @@ public interface WebViewProvider {
interface ViewDelegate {
public boolean shouldDelayChildPressedState();
+ public void onProvideVirtualAssistStructure(ViewAssistStructure structure);
+
public AccessibilityNodeProvider getAccessibilityNodeProvider();
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 168066a..e7b6238 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -61,6 +61,7 @@ import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
@@ -1504,11 +1505,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
super.onInitializeAccessibilityNodeInfoInternal(info);
if (isEnabled()) {
if (canScrollUp()) {
- info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ info.addAction(AccessibilityAction.ACTION_SCROLL_BACKWARD);
info.setScrollable(true);
}
if (canScrollDown()) {
- info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ info.addAction(AccessibilityAction.ACTION_SCROLL_FORWARD);
info.setScrollable(true);
}
}
@@ -2502,18 +2503,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (position == getSelectedItemPosition()) {
info.setSelected(true);
- info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION);
+ info.addAction(AccessibilityAction.ACTION_CLEAR_SELECTION);
} else {
- info.addAction(AccessibilityNodeInfo.ACTION_SELECT);
+ info.addAction(AccessibilityAction.ACTION_SELECT);
}
if (isClickable()) {
- info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+ info.addAction(AccessibilityAction.ACTION_CLICK);
info.setClickable(true);
}
if (isLongClickable()) {
- info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
+ info.addAction(AccessibilityAction.ACTION_LONG_CLICK);
info.setLongClickable(true);
}
}
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 4fadc19..36bce0b 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -16,6 +16,10 @@
package android.widget;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -24,6 +28,7 @@ import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.ActionProvider;
import android.view.Gravity;
@@ -32,6 +37,7 @@ import android.view.SoundEffectConstants;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ListPopupWindow.ForwardingListener;
import com.android.internal.transition.ActionBarTransition;
@@ -45,6 +51,7 @@ import com.android.internal.view.menu.MenuView;
import com.android.internal.view.menu.SubMenuBuilder;
import java.util.ArrayList;
+import java.util.List;
/**
* MenuPresenter for building action menus as seen in the action bar and action modes.
@@ -54,6 +61,7 @@ import java.util.ArrayList;
public class ActionMenuPresenter extends BaseMenuPresenter
implements ActionProvider.SubUiVisibilityListener {
private static final String TAG = "ActionMenuPresenter";
+ private static final int ITEM_ANIMATION_DURATION = 150;
private OverflowMenuButton mOverflowButton;
private boolean mReserveOverflow;
@@ -71,8 +79,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter
// Group IDs that have been added as actions - used temporarily, allocated here for reuse.
private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
- private View mScrapActionButtonView;
-
private OverflowPopup mOverflowPopup;
private ActionButtonSubmenu mActionButtonPopup;
@@ -84,6 +90,18 @@ public class ActionMenuPresenter extends BaseMenuPresenter
final PopupPresenterCallback mPopupPresenterCallback = new PopupPresenterCallback();
int mOpenSubMenuId;
+ // These collections are used to store pre- and post-layout information for menu items,
+ // which is used to determine appropriate animations to run for changed items.
+ private SparseArray<MenuItemLayoutInfo> mPreLayoutItems =
+ new SparseArray<MenuItemLayoutInfo>();
+ private SparseArray<MenuItemLayoutInfo> mPostLayoutItems =
+ new SparseArray<MenuItemLayoutInfo>();
+
+ // The list of currently running animations on menu items.
+ private List<ItemAnimationInfo> mRunningItemAnimations = new ArrayList<ItemAnimationInfo>();
+
+
+
public ActionMenuPresenter(Context context) {
super(context, com.android.internal.R.layout.action_menu_layout,
com.android.internal.R.layout.action_menu_item_layout);
@@ -125,9 +143,6 @@ public class ActionMenuPresenter extends BaseMenuPresenter
mActionItemWidthLimit = width;
mMinCellSize = (int) (ActionMenuView.MIN_CELL_SIZE * res.getDisplayMetrics().density);
-
- // Drop a scrap view as it may no longer reflect the proper context/config.
- mScrapActionButtonView = null;
}
public void onConfigurationChanged(Configuration newConfig) {
@@ -202,10 +217,187 @@ public class ActionMenuPresenter extends BaseMenuPresenter
return item.isActionButton();
}
+ /**
+ * Store layout information about current items in the menu. This is stored for
+ * both pre- and post-layout phases and compared in runItemAnimations() to determine
+ * the animations that need to be run on any item changes.
+ *
+ * @param preLayout Whether this is being called in the pre-layout phase. This is passed
+ * into the MenuItemLayoutInfo structure to store the appropriate position values.
+ */
+ private void computeMenuItemAnimationInfo(boolean preLayout) {
+ final ViewGroup menuViewParent = (ViewGroup) mMenuView;
+ final int count = menuViewParent.getChildCount();
+ SparseArray items = preLayout ? mPreLayoutItems : mPostLayoutItems;
+ for (int i = 0; i < count; ++i) {
+ View child = menuViewParent.getChildAt(i);
+ final int id = child.getId();
+ if (id > 0 && child.getWidth() != 0 && child.getHeight() != 0) {
+ MenuItemLayoutInfo info = new MenuItemLayoutInfo(child, preLayout);
+ items.put(id, info);
+ }
+ }
+ }
+
+ /**
+ * This method is called once both the pre-layout and post-layout steps have
+ * happened. It figures out which views are new (didn't exist prior to layout),
+ * gone (existed pre-layout, but are now gone), or changed (exist in both,
+ * but in a different location) and runs appropriate animations on those views.
+ * Items are tracked by ids, since the underlying views that represent items
+ * pre- and post-layout may be different.
+ */
+ private void runItemAnimations() {
+ for (int i = 0; i < mPreLayoutItems.size(); ++i) {
+ int id = mPreLayoutItems.keyAt(i);
+ final MenuItemLayoutInfo menuItemLayoutInfoPre = mPreLayoutItems.get(id);
+ final int postLayoutIndex = mPostLayoutItems.indexOfKey(id);
+ if (postLayoutIndex >= 0) {
+ // item exists pre and post: see if it's changed
+ final MenuItemLayoutInfo menuItemLayoutInfoPost =
+ mPostLayoutItems.valueAt(postLayoutIndex);
+ PropertyValuesHolder pvhX = null;
+ PropertyValuesHolder pvhY = null;
+ if (menuItemLayoutInfoPre.left != menuItemLayoutInfoPost.left) {
+ pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X,
+ (menuItemLayoutInfoPre.left - menuItemLayoutInfoPost.left), 0);
+ }
+ if (menuItemLayoutInfoPre.top != menuItemLayoutInfoPost.top) {
+ pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y,
+ menuItemLayoutInfoPre.top - menuItemLayoutInfoPost.top, 0);
+ }
+ if (pvhX != null || pvhY != null) {
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j);
+ if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.MOVE) {
+ oldInfo.animator.cancel();
+ }
+ }
+ ObjectAnimator anim;
+ if (pvhX != null) {
+ if (pvhY != null) {
+ anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view,
+ pvhX, pvhY);
+ } else {
+ anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view, pvhX);
+ }
+ } else {
+ anim = ObjectAnimator.ofPropertyValuesHolder(menuItemLayoutInfoPost.view, pvhY);
+ }
+ anim.setDuration(ITEM_ANIMATION_DURATION);
+ anim.start();
+ ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfoPost, anim,
+ ItemAnimationInfo.MOVE);
+ mRunningItemAnimations.add(info);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ if (mRunningItemAnimations.get(j).animator == animation) {
+ mRunningItemAnimations.remove(j);
+ break;
+ }
+ }
+ }
+ });
+ }
+ mPostLayoutItems.remove(id);
+ } else {
+ // item used to be there, is now gone
+ float oldAlpha = 1;
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j);
+ if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.FADE_IN) {
+ oldAlpha = oldInfo.menuItemLayoutInfo.view.getAlpha();
+ oldInfo.animator.cancel();
+ }
+ }
+ ObjectAnimator anim = ObjectAnimator.ofFloat(menuItemLayoutInfoPre.view, View.ALPHA,
+ oldAlpha, 0);
+ // Re-using the view from pre-layout assumes no view recycling
+ ((ViewGroup) mMenuView).getOverlay().add(menuItemLayoutInfoPre.view);
+ anim.setDuration(ITEM_ANIMATION_DURATION);
+ anim.start();
+ ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfoPre, anim, ItemAnimationInfo.FADE_OUT);
+ mRunningItemAnimations.add(info);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ if (mRunningItemAnimations.get(j).animator == animation) {
+ mRunningItemAnimations.remove(j);
+ break;
+ }
+ }
+ ((ViewGroup) mMenuView).getOverlay().remove(menuItemLayoutInfoPre.view);
+ }
+ });
+ }
+ }
+ for (int i = 0; i < mPostLayoutItems.size(); ++i) {
+ int id = mPostLayoutItems.keyAt(i);
+ final int postLayoutIndex = mPostLayoutItems.indexOfKey(id);
+ if (postLayoutIndex >= 0) {
+ // item is new
+ final MenuItemLayoutInfo menuItemLayoutInfo =
+ mPostLayoutItems.valueAt(postLayoutIndex);
+ float oldAlpha = 0;
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ ItemAnimationInfo oldInfo = mRunningItemAnimations.get(j);
+ if (oldInfo.id == id && oldInfo.animType == ItemAnimationInfo.FADE_OUT) {
+ oldAlpha = oldInfo.menuItemLayoutInfo.view.getAlpha();
+ oldInfo.animator.cancel();
+ }
+ }
+ ObjectAnimator anim = ObjectAnimator.ofFloat(menuItemLayoutInfo.view, View.ALPHA,
+ oldAlpha, 1);
+ anim.start();
+ anim.setDuration(ITEM_ANIMATION_DURATION);
+ ItemAnimationInfo info = new ItemAnimationInfo(id, menuItemLayoutInfo, anim, ItemAnimationInfo.FADE_IN);
+ mRunningItemAnimations.add(info);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (int j = 0; j < mRunningItemAnimations.size(); ++j) {
+ if (mRunningItemAnimations.get(j).animator == animation) {
+ mRunningItemAnimations.remove(j);
+ break;
+ }
+ }
+ }
+ });
+ }
+ }
+ mPreLayoutItems.clear();
+ mPostLayoutItems.clear();
+ }
+
+ /**
+ * Gets position/existence information on menu items before and after layout,
+ * which is then fed into runItemAnimations()
+ */
+ private void setupItemAnimations() {
+ final ViewGroup menuViewParent = (ViewGroup) mMenuView;
+ computeMenuItemAnimationInfo(true);
+ final ViewTreeObserver observer = menuViewParent.getViewTreeObserver();
+ if (observer != null) {
+ observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ computeMenuItemAnimationInfo(false);
+ observer.removeOnPreDrawListener(this);
+ runItemAnimations();
+ return true;
+ }
+ });
+ }
+ }
+
@Override
public void updateMenuView(boolean cleared) {
final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent();
if (menuViewParent != null) {
+ setupItemAnimations();
ActionBarTransition.beginDelayedTransition(menuViewParent);
}
super.updateMenuView(cleared);
@@ -431,10 +623,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
MenuItemImpl item = visibleItems.get(i);
if (item.requiresActionButton()) {
- View v = getItemView(item, mScrapActionButtonView, parent);
- if (mScrapActionButtonView == null) {
- mScrapActionButtonView = v;
- }
+ View v = getItemView(item, null, parent);
if (mStrictWidthLimit) {
cellsRemaining -= ActionMenuView.measureChildForCells(v,
cellSize, cellsRemaining, querySpec, 0);
@@ -460,10 +649,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter
(!mStrictWidthLimit || cellsRemaining > 0);
if (isAction) {
- View v = getItemView(item, mScrapActionButtonView, parent);
- if (mScrapActionButtonView == null) {
- mScrapActionButtonView = v;
- }
+ View v = getItemView(item, null, parent);
if (mStrictWidthLimit) {
final int cells = ActionMenuView.measureChildForCells(v,
cellSize, cellsRemaining, querySpec, 0);
@@ -819,4 +1005,53 @@ public class ActionMenuPresenter extends BaseMenuPresenter
boolean mHasTintMode;
boolean mHasTintList;
}
+
+ /**
+ * This class holds layout information for a menu item. This is used to determine
+ * pre- and post-layout information about menu items, which will then be used to
+ * determine appropriate item animations.
+ */
+ private static class MenuItemLayoutInfo {
+ View view;
+ int left;
+ int top;
+
+ MenuItemLayoutInfo(View view, boolean preLayout) {
+ left = view.getLeft();
+ top = view.getTop();
+ if (preLayout) {
+ // We track translation for pre-layout because a view might be mid-animation
+ // and we need this information to know where to animate from
+ left += view.getTranslationX();
+ top += view.getTranslationY();
+ }
+ this.view = view;
+ }
+ }
+
+ /**
+ * This class is used to store information about currently-running item animations.
+ * This is used when new animations are scheduled to determine whether any existing
+ * animations need to be canceled, based on whether the running animations overlap
+ * with any new animations. For example, if an item is currently animating from
+ * location A to B and another change dictates that it be animated to C, then the current
+ * A-B animation will be canceled and a new animation to C will be started.
+ */
+ private static class ItemAnimationInfo {
+ int id;
+ MenuItemLayoutInfo menuItemLayoutInfo;
+ Animator animator;
+ int animType;
+ static final int MOVE = 0;
+ static final int FADE_IN = 1;
+ static final int FADE_OUT = 2;
+
+ ItemAnimationInfo(int id, MenuItemLayoutInfo info, Animator anim, int animType) {
+ this.id = id;
+ menuItemLayoutInfo = info;
+ animator = anim;
+ this.animType = animType;
+ }
+ }
+
}
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 1716dbd..45eee34 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -40,8 +40,10 @@ import java.util.TimeZone;
* @attr ref android.R.styleable#AnalogClock_dial
* @attr ref android.R.styleable#AnalogClock_hand_hour
* @attr ref android.R.styleable#AnalogClock_hand_minute
+ * @deprecated This widget is no longer supported.
*/
@RemoteView
+@Deprecated
public class AnalogClock extends View {
private Time mCalendar;
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index 7b8a979..a157087 100755
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -49,7 +49,6 @@ import java.util.Locale;
* A delegate for picking up a date (day / month / year).
*/
class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
-
private static final int USE_LOCALE = 0;
private static final int UNINITIALIZED = -1;
@@ -61,9 +60,9 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
private static final int ANIMATION_DURATION = 300;
- public static final int[] ATTRS_TEXT_COLOR = new int[]{com.android.internal.R.attr.textColor};
-
- public static final int[] ATTRS_DISABLED_ALPHA = new int[]{
+ private static final int[] ATTRS_TEXT_COLOR = new int[] {
+ com.android.internal.R.attr.textColor};
+ private static final int[] ATTRS_DISABLED_ALPHA = new int[] {
com.android.internal.R.attr.disabledAlpha};
private SimpleDateFormat mYearFormat;
@@ -157,6 +156,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
header.setBackground(a.getDrawable(R.styleable.DatePicker_headerBackground));
}
+ a.recycle();
+
// Set up picker container.
mAnimator = (ViewAnimator) mContainer.findViewById(R.id.animator);
@@ -174,32 +175,10 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
mYearPickerView.setDate(mCurrentDate.getTimeInMillis());
mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener);
- final int yearTextAppearanceResId = a.getResourceId(
- R.styleable.DatePicker_yearListItemTextAppearance, 0);
- if (yearTextAppearanceResId != 0) {
- mYearPickerView.setYearTextAppearance(yearTextAppearanceResId);
- }
-
- final int yearActivatedTextAppearanceResId = a.getResourceId(
- R.styleable.DatePicker_yearListItemActivatedTextAppearance, 0);
- if (yearActivatedTextAppearanceResId != 0) {
- mYearPickerView.setYearActivatedTextAppearance(yearActivatedTextAppearanceResId);
- }
-
- a.recycle();
-
// Set up content descriptions.
mSelectDay = res.getString(R.string.select_day);
mSelectYear = res.getString(R.string.select_year);
- final Animation inAnim = new AlphaAnimation(0, 1);
- inAnim.setDuration(ANIMATION_DURATION);
- mAnimator.setInAnimation(inAnim);
-
- final Animation outAnim = new AlphaAnimation(1, 0);
- outAnim.setDuration(ANIMATION_DURATION);
- mAnimator.setOutAnimation(outAnim);
-
// Initialize for current locale. This also initializes the date, so no
// need to call onDateChanged.
onLocaleChanged(mCurrentLocale);
diff --git a/core/java/android/widget/DayPickerAdapter.java b/core/java/android/widget/DayPickerAdapter.java
index 4f9f09e..9a4b6f5 100644
--- a/core/java/android/widget/DayPickerAdapter.java
+++ b/core/java/android/widget/DayPickerAdapter.java
@@ -18,10 +18,15 @@ package android.widget;
import com.android.internal.widget.PagerAdapter;
+import android.annotation.IdRes;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.util.SparseArray;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SimpleMonthView.OnDayClickListener;
@@ -37,9 +42,13 @@ class DayPickerAdapter extends PagerAdapter {
private final Calendar mMinDate = Calendar.getInstance();
private final Calendar mMaxDate = Calendar.getInstance();
- private final SparseArray<SimpleMonthView> mItems = new SparseArray<>();
+ private final SparseArray<ViewHolder> mItems = new SparseArray<>();
- private Calendar mSelectedDay = Calendar.getInstance();
+ private final LayoutInflater mInflater;
+ private final int mLayoutResId;
+ private final int mCalendarViewId;
+
+ private Calendar mSelectedDay = null;
private int mMonthTextAppearance;
private int mDayOfWeekTextAppearance;
@@ -51,19 +60,29 @@ class DayPickerAdapter extends PagerAdapter {
private OnDaySelectedListener mOnDaySelectedListener;
+ private int mCount;
private int mFirstDayOfWeek;
- public DayPickerAdapter(Context context) {
+ public DayPickerAdapter(@NonNull Context context, @LayoutRes int layoutResId,
+ @IdRes int calendarViewId) {
+ mInflater = LayoutInflater.from(context);
+ mLayoutResId = layoutResId;
+ mCalendarViewId = calendarViewId;
+
final TypedArray ta = context.obtainStyledAttributes(new int[] {
com.android.internal.R.attr.colorControlHighlight});
mDayHighlightColor = ta.getColorStateList(0);
ta.recycle();
}
- public void setRange(Calendar min, Calendar max) {
+ public void setRange(@NonNull Calendar min, @NonNull Calendar max) {
mMinDate.setTimeInMillis(min.getTimeInMillis());
mMaxDate.setTimeInMillis(max.getTimeInMillis());
+ final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR);
+ final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH);
+ mCount = diffMonth + MONTHS_IN_YEAR * diffYear + 1;
+
// Positions are now invalid, clear everything and start over.
notifyDataSetChanged();
}
@@ -80,7 +99,7 @@ class DayPickerAdapter extends PagerAdapter {
// Update displayed views.
final int count = mItems.size();
for (int i = 0; i < count; i++) {
- final SimpleMonthView monthView = mItems.valueAt(i);
+ final SimpleMonthView monthView = mItems.valueAt(i).calendar;
monthView.setFirstDayOfWeek(weekStart);
}
}
@@ -94,23 +113,25 @@ class DayPickerAdapter extends PagerAdapter {
*
* @param day the selected day
*/
- public void setSelectedDay(Calendar day) {
+ public void setSelectedDay(@Nullable Calendar day) {
final int oldPosition = getPositionForDay(mSelectedDay);
final int newPosition = getPositionForDay(day);
// Clear the old position if necessary.
- if (oldPosition != newPosition) {
- final SimpleMonthView oldMonthView = mItems.get(oldPosition, null);
+ if (oldPosition != newPosition && oldPosition >= 0) {
+ final ViewHolder oldMonthView = mItems.get(oldPosition, null);
if (oldMonthView != null) {
- oldMonthView.setSelectedDay(-1);
+ oldMonthView.calendar.setSelectedDay(-1);
}
}
// Set the new position.
- final SimpleMonthView newMonthView = mItems.get(newPosition, null);
- if (newMonthView != null) {
- final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH);
- newMonthView.setSelectedDay(dayOfMonth);
+ if (newPosition >= 0) {
+ final ViewHolder newMonthView = mItems.get(newPosition, null);
+ if (newMonthView != null) {
+ final int dayOfMonth = day.get(Calendar.DAY_OF_MONTH);
+ newMonthView.calendar.setSelectedDay(dayOfMonth);
+ }
}
mSelectedDay = day;
@@ -155,14 +176,13 @@ class DayPickerAdapter extends PagerAdapter {
@Override
public int getCount() {
- final int diffYear = mMaxDate.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR);
- final int diffMonth = mMaxDate.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH);
- return diffMonth + MONTHS_IN_YEAR * diffYear + 1;
+ return mCount;
}
@Override
public boolean isViewFromObject(View view, Object object) {
- return view == object;
+ final ViewHolder holder = (ViewHolder) object;
+ return view == holder.container;
}
private int getMonthForPosition(int position) {
@@ -173,7 +193,11 @@ class DayPickerAdapter extends PagerAdapter {
return position / MONTHS_IN_YEAR + mMinDate.get(Calendar.YEAR);
}
- private int getPositionForDay(Calendar day) {
+ private int getPositionForDay(@Nullable Calendar day) {
+ if (day == null) {
+ return -1;
+ }
+
final int yearOffset = (day.get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR));
final int monthOffset = (day.get(Calendar.MONTH) - mMinDate.get(Calendar.MONTH));
return yearOffset * MONTHS_IN_YEAR + monthOffset;
@@ -181,7 +205,9 @@ class DayPickerAdapter extends PagerAdapter {
@Override
public Object instantiateItem(ViewGroup container, int position) {
- final SimpleMonthView v = new SimpleMonthView(container.getContext());
+ final View itemView = mInflater.inflate(mLayoutResId, container, false);
+
+ final SimpleMonthView v = (SimpleMonthView) itemView.findViewById(mCalendarViewId);
v.setOnDayClickListener(mOnDayClickListener);
v.setMonthTextAppearance(mMonthTextAppearance);
v.setDayOfWeekTextAppearance(mDayOfWeekTextAppearance);
@@ -205,7 +231,7 @@ class DayPickerAdapter extends PagerAdapter {
final int year = getYearForPosition(position);
final int selectedDay;
- if (mSelectedDay.get(Calendar.MONTH) == month) {
+ if (mSelectedDay != null && mSelectedDay.get(Calendar.MONTH) == month) {
selectedDay = mSelectedDay.get(Calendar.DAY_OF_MONTH);
} else {
selectedDay = -1;
@@ -227,33 +253,34 @@ class DayPickerAdapter extends PagerAdapter {
v.setMonthParams(selectedDay, month, year, mFirstDayOfWeek,
enabledDayRangeStart, enabledDayRangeEnd);
+ v.setPrevEnabled(position > 0);
+ v.setNextEnabled(position < mCount - 1);
- mItems.put(position, v);
+ final ViewHolder holder = new ViewHolder(position, itemView, v);
+ mItems.put(position, holder);
- container.addView(v);
+ container.addView(itemView);
- return v;
+ return holder;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
- container.removeView(mItems.get(position));
+ final ViewHolder holder = (ViewHolder) object;
+ container.removeView(holder.container);
mItems.remove(position);
}
@Override
public int getItemPosition(Object object) {
- final int index = mItems.indexOfValue((SimpleMonthView) object);
- if (index < 0) {
- return mItems.keyAt(index);
- }
- return -1;
+ final ViewHolder holder = (ViewHolder) object;
+ return holder.position;
}
@Override
public CharSequence getPageTitle(int position) {
- final SimpleMonthView v = mItems.get(position);
+ final SimpleMonthView v = mItems.get(position).calendar;
if (v != null) {
return v.getTitle();
}
@@ -275,9 +302,29 @@ class DayPickerAdapter extends PagerAdapter {
}
}
}
+
+ @Override
+ public void onNavigationClick(SimpleMonthView view, int direction, boolean animate) {
+ if (mOnDaySelectedListener != null) {
+ mOnDaySelectedListener.onNavigationClick(DayPickerAdapter.this, direction, animate);
+ }
+ }
};
+ private static class ViewHolder {
+ public final int position;
+ public final View container;
+ public final SimpleMonthView calendar;
+
+ public ViewHolder(int position, View container, SimpleMonthView calendar) {
+ this.position = position;
+ this.container = container;
+ this.calendar = calendar;
+ }
+ }
+
public interface OnDaySelectedListener {
public void onDaySelected(DayPickerAdapter view, Calendar day);
+ public void onNavigationClick(DayPickerAdapter view, int direction, boolean animate);
}
}
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index a7ae926..ec2528f 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -88,7 +88,8 @@ class DayPickerView extends ViewPager {
a.recycle();
// Set up adapter.
- mAdapter = new DayPickerAdapter(context);
+ mAdapter = new DayPickerAdapter(context,
+ R.layout.date_picker_month_item_material, R.id.month_view);
mAdapter.setMonthTextAppearance(monthTextAppearanceResId);
mAdapter.setDayOfWeekTextAppearance(dayOfWeekTextAppearanceResId);
mAdapter.setDayTextAppearance(dayTextAppearanceResId);
@@ -128,6 +129,14 @@ class DayPickerView extends ViewPager {
mOnDaySelectedListener.onDaySelected(DayPickerView.this, day);
}
}
+
+ @Override
+ public void onNavigationClick(DayPickerAdapter view, int direction, boolean animate) {
+ // ViewPager clamps input values, so we don't need to worry
+ // about passing invalid indices.
+ final int nextItem = getCurrentItem() + direction;
+ setCurrentItem(nextItem, animate);
+ }
});
}
@@ -178,7 +187,6 @@ class DayPickerView extends ViewPager {
* @param setSelected whether to set the specified day as selected
*/
private void setDate(long timeInMillis, boolean animate, boolean setSelected) {
- // Set the selected day
if (setSelected) {
mSelectedDay.setTimeInMillis(timeInMillis);
}
@@ -187,6 +195,9 @@ class DayPickerView extends ViewPager {
if (position != getCurrentItem()) {
setCurrentItem(position, animate);
}
+
+ mTempCalendar.setTimeInMillis(timeInMillis);
+ mAdapter.setSelectedDay(mTempCalendar);
}
public long getDate() {
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 87fcd81..32b99a8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -16,18 +16,6 @@
package android.widget;
-import android.content.UndoManager;
-import android.content.UndoOperation;
-import android.content.UndoOwner;
-import android.os.Build;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.InputFilter;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.GrowingArrayUtils;
-import com.android.internal.view.menu.MenuBuilder;
-import com.android.internal.widget.EditableInputConnection;
-
import android.R;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
@@ -35,6 +23,9 @@ import android.content.ClipData;
import android.content.ClipData.Item;
import android.content.Context;
import android.content.Intent;
+import android.content.UndoManager;
+import android.content.UndoOperation;
+import android.content.UndoOwner;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -46,13 +37,17 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.ExtractEditText;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.ParcelableParcel;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.DynamicLayout;
import android.text.Editable;
+import android.text.InputFilter;
import android.text.InputType;
import android.text.Layout;
import android.text.ParcelableSpan;
@@ -105,6 +100,11 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.TextView.Drawables;
import android.widget.TextView.OnEditorActionListener;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.widget.EditableInputConnection;
+
import java.text.BreakIterator;
import java.util.Arrays;
import java.util.Comparator;
@@ -2939,7 +2939,27 @@ public class Editor {
* The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
* on which of these this TextView supports.
*/
- private class SelectionActionModeCallback implements ActionMode.Callback {
+ private class SelectionActionModeCallback extends ActionMode.Callback2 {
+ private final Path mSelectionPath = new Path();
+ private final RectF mSelectionBounds = new RectF();
+
+ private int mSelectionHandleHeight;
+ private int mInsertionHandleHeight;
+
+ public SelectionActionModeCallback() {
+ SelectionModifierCursorController selectionController = getSelectionController();
+ if (selectionController.mStartHandle == null) {
+ selectionController.initDrawables();
+ selectionController.initHandles();
+ }
+ mSelectionHandleHeight = Math.max(
+ mSelectHandleLeft.getMinimumHeight(), mSelectHandleRight.getMinimumHeight());
+ InsertionPointCursorController insertionController = getInsertionController();
+ if (insertionController != null) {
+ insertionController.getHandle();
+ mInsertionHandleHeight = mSelectHandleCenter.getMinimumHeight();
+ }
+ }
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
@@ -2956,13 +2976,6 @@ public class Editor {
mode.setSubtitle(null);
mode.setTitleOptionalHint(true);
- menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
- setIcon(styledAttributes.getResourceId(
- R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0)).
- setAlphabeticShortcut('a').
- setShowAsAction(
- MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
-
if (mTextView.canCut()) {
menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut).
setIcon(styledAttributes.getResourceId(
@@ -2990,6 +3003,13 @@ public class Editor {
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
+ menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
+ setIcon(styledAttributes.getResourceId(
+ R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0)).
+ setAlphabeticShortcut('a').
+ setShowAsAction(
+ MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+
if (mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan()) {
menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
setShowAsAction(
@@ -3057,6 +3077,40 @@ public class Editor {
mSelectionActionMode = null;
}
+
+ @Override
+ public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+ if (!view.equals(mTextView) || mTextView.getLayout() == null) {
+ super.onGetContentRect(mode, view, outRect);
+ return;
+ }
+ if (mTextView.getSelectionStart() != mTextView.getSelectionEnd()) {
+ // We have a selection.
+ mSelectionPath.reset();
+ mTextView.getLayout().getSelectionPath(
+ mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath);
+ mSelectionPath.computeBounds(mSelectionBounds, true);
+ mSelectionBounds.bottom += mSelectionHandleHeight;
+ } else {
+ // We have a single cursor.
+ int line = mTextView.getLayout().getLineForOffset(mTextView.getSelectionStart());
+ float primaryHorizontal =
+ mTextView.getLayout().getPrimaryHorizontal(mTextView.getSelectionStart());
+ mSelectionBounds.set(
+ primaryHorizontal,
+ mTextView.getLayout().getLineTop(line),
+ primaryHorizontal + 1,
+ mTextView.getLayout().getLineTop(line + 1) + mInsertionHandleHeight);
+ }
+ // Take TextView's padding and scroll into account.
+ int textHorizontalOffset = mTextView.viewportToContentHorizontalOffset();
+ int textVerticalOffset = mTextView.viewportToContentVerticalOffset();
+ outRect.set(
+ (int) Math.floor(mSelectionBounds.left + textHorizontalOffset),
+ (int) Math.floor(mSelectionBounds.top + textVerticalOffset),
+ (int) Math.ceil(mSelectionBounds.right + textHorizontalOffset),
+ (int) Math.ceil(mSelectionBounds.bottom + textVerticalOffset));
+ }
}
private void onReplace() {
@@ -3066,97 +3120,6 @@ public class Editor {
showSuggestions();
}
- private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener {
- private static final int POPUP_TEXT_LAYOUT =
- com.android.internal.R.layout.text_edit_action_popup_text;
- private TextView mPasteTextView;
- private TextView mReplaceTextView;
-
- @Override
- protected void createPopupWindow() {
- mPopupWindow = new PopupWindow(mTextView.getContext(), null,
- com.android.internal.R.attr.textSelectHandleWindowStyle);
- mPopupWindow.setClippingEnabled(true);
- }
-
- @Override
- protected void initContentView() {
- LinearLayout linearLayout = new LinearLayout(mTextView.getContext());
- linearLayout.setOrientation(LinearLayout.HORIZONTAL);
- mContentView = linearLayout;
- mContentView.setBackgroundResource(
- com.android.internal.R.drawable.text_edit_paste_window);
-
- LayoutInflater inflater = (LayoutInflater) mTextView.getContext().
- getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- LayoutParams wrapContent = new LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-
- mPasteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
- mPasteTextView.setLayoutParams(wrapContent);
- mContentView.addView(mPasteTextView);
- mPasteTextView.setText(com.android.internal.R.string.paste);
- mPasteTextView.setOnClickListener(this);
-
- mReplaceTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
- mReplaceTextView.setLayoutParams(wrapContent);
- mContentView.addView(mReplaceTextView);
- mReplaceTextView.setText(com.android.internal.R.string.replace);
- mReplaceTextView.setOnClickListener(this);
- }
-
- @Override
- public void show() {
- boolean canPaste = mTextView.canPaste();
- boolean canSuggest = mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan();
- mPasteTextView.setVisibility(canPaste ? View.VISIBLE : View.GONE);
- mReplaceTextView.setVisibility(canSuggest ? View.VISIBLE : View.GONE);
-
- if (!canPaste && !canSuggest) return;
-
- super.show();
- }
-
- @Override
- public void onClick(View view) {
- if (view == mPasteTextView && mTextView.canPaste()) {
- mTextView.onTextContextMenuItem(TextView.ID_PASTE);
- hide();
- } else if (view == mReplaceTextView) {
- onReplace();
- }
- }
-
- @Override
- protected int getTextOffset() {
- return (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2;
- }
-
- @Override
- protected int getVerticalLocalPosition(int line) {
- return mTextView.getLayout().getLineTop(line) - mContentView.getMeasuredHeight();
- }
-
- @Override
- protected int clipVertically(int positionY) {
- if (positionY < 0) {
- final int offset = getTextOffset();
- final Layout layout = mTextView.getLayout();
- final int line = layout.getLineForOffset(offset);
- positionY += layout.getLineBottom(line) - layout.getLineTop(line);
- positionY += mContentView.getMeasuredHeight();
-
- // Assumes insertion and selection handles share the same height
- final Drawable handle = mTextView.getContext().getDrawable(
- mTextView.mTextSelectHandleRes);
- positionY += handle.getIntrinsicHeight();
- }
-
- return positionY;
- }
- }
-
/**
* A listener to call {@link InputMethodManager#updateCursorAnchorInfo(View, CursorAnchorInfo)}
* while the input method is requesting the cursor/anchor position. Does nothing as long as
@@ -4102,7 +4065,6 @@ public class Editor {
}
class SelectionModifierCursorController implements CursorController {
- private static final int DELAY_BEFORE_REPLACE_ACTION = 200; // milliseconds
// The cursor controller handles, lazily created when shown.
private SelectionStartHandleView mStartHandle;
private SelectionEndHandleView mEndHandle;
@@ -4664,8 +4626,8 @@ public class Editor {
// Otherwise the user inserted the composition.
String newText = TextUtils.substring(source, start, end);
- EditOperation edit = new EditOperation(mEditor, false, "", dstart, newText);
- recordEdit(edit);
+ EditOperation edit = new EditOperation(mEditor, "", dstart, newText);
+ recordEdit(edit, false /* forceMerge */);
return true;
}
@@ -4684,11 +4646,15 @@ public class Editor {
// Build a new operation with all the information from this edit.
String newText = TextUtils.substring(source, start, end);
String oldText = TextUtils.substring(dest, dstart, dend);
- EditOperation edit = new EditOperation(mEditor, forceMerge, oldText, dstart, newText);
- recordEdit(edit);
+ EditOperation edit = new EditOperation(mEditor, oldText, dstart, newText);
+ recordEdit(edit, forceMerge);
}
- private void recordEdit(EditOperation edit) {
+ /**
+ * Fetches the last undo operation and checks to see if a new edit should be merged into it.
+ * If forceMerge is true then the new edit is always merged.
+ */
+ private void recordEdit(EditOperation edit, boolean forceMerge) {
// Fetch the last edit operation and attempt to merge in the new edit.
final UndoManager um = mEditor.mUndoManager;
um.beginUpdate("Edit text");
@@ -4698,6 +4664,11 @@ public class Editor {
// Add this as the first edit.
if (DEBUG_UNDO) Log.d(TAG, "filter: adding first op " + edit);
um.addOperation(edit, UndoManager.MERGE_MODE_NONE);
+ } else if (forceMerge) {
+ // Forced merges take priority because they could be the result of a non-user-edit
+ // change and this case should not create a new undo operation.
+ if (DEBUG_UNDO) Log.d(TAG, "filter: force merge " + edit);
+ lastEdit.forceMergeWith(edit);
} else if (!mIsUserEdit) {
// An application directly modified the Editable outside of a text edit. Treat this
// as a new change and don't attempt to merge.
@@ -4773,7 +4744,6 @@ public class Editor {
private static final int TYPE_REPLACE = 2;
private int mType;
- private boolean mForceMerge;
private String mOldText;
private int mOldTextStart;
private String mNewText;
@@ -4784,13 +4754,10 @@ public class Editor {
/**
* Constructs an edit operation from a text input operation on editor that replaces the
- * oldText starting at dstart with newText. If forceMerge is true then always forcibly
- * merge this operation with any previous one.
+ * oldText starting at dstart with newText.
*/
- public EditOperation(Editor editor, boolean forceMerge, String oldText, int dstart,
- String newText) {
+ public EditOperation(Editor editor, String oldText, int dstart, String newText) {
super(editor.mUndoOwner);
- mForceMerge = forceMerge;
mOldText = oldText;
mNewText = newText;
@@ -4817,7 +4784,6 @@ public class Editor {
public EditOperation(Parcel src, ClassLoader loader) {
super(src, loader);
mType = src.readInt();
- mForceMerge = src.readInt() != 0;
mOldText = src.readString();
mOldTextStart = src.readInt();
mNewText = src.readString();
@@ -4829,7 +4795,6 @@ public class Editor {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
- dest.writeInt(mForceMerge ? 1 : 0);
dest.writeString(mOldText);
dest.writeInt(mOldTextStart);
dest.writeString(mNewText);
@@ -4881,10 +4846,6 @@ public class Editor {
Log.d(TAG, "mergeWith old " + this);
Log.d(TAG, "mergeWith new " + edit);
}
- if (edit.mForceMerge) {
- forceMergeWith(edit);
- return true;
- }
switch (mType) {
case TYPE_INSERT:
return mergeInsertWith(edit);
@@ -4942,7 +4903,7 @@ public class Editor {
* Forcibly creates a single merged edit operation by simulating the entire text
* contents being replaced.
*/
- private void forceMergeWith(EditOperation edit) {
+ public void forceMergeWith(EditOperation edit) {
if (DEBUG_UNDO) Log.d(TAG, "forceMerge");
Editor editor = getOwnerData();
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index c6e3dc8..be0ca71 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Rect;
+import android.os.Bundle;
import android.os.Trace;
import android.util.AttributeSet;
import android.util.MathUtils;
@@ -32,12 +33,15 @@ import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
import android.view.animation.GridLayoutAnimationController;
import android.widget.RemoteViews.RemoteView;
+import com.android.internal.R;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -117,7 +121,7 @@ public class GridView extends AbsListView {
}
public GridView(Context context, AttributeSet attrs) {
- this(context, attrs, com.android.internal.R.attr.gridViewStyle);
+ this(context, attrs, R.attr.gridViewStyle);
}
public GridView(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -128,30 +132,30 @@ public class GridView extends AbsListView {
super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.GridView, defStyleAttr, defStyleRes);
+ attrs, R.styleable.GridView, defStyleAttr, defStyleRes);
int hSpacing = a.getDimensionPixelOffset(
- com.android.internal.R.styleable.GridView_horizontalSpacing, 0);
+ R.styleable.GridView_horizontalSpacing, 0);
setHorizontalSpacing(hSpacing);
int vSpacing = a.getDimensionPixelOffset(
- com.android.internal.R.styleable.GridView_verticalSpacing, 0);
+ R.styleable.GridView_verticalSpacing, 0);
setVerticalSpacing(vSpacing);
- int index = a.getInt(com.android.internal.R.styleable.GridView_stretchMode, STRETCH_COLUMN_WIDTH);
+ int index = a.getInt(R.styleable.GridView_stretchMode, STRETCH_COLUMN_WIDTH);
if (index >= 0) {
setStretchMode(index);
}
- int columnWidth = a.getDimensionPixelOffset(com.android.internal.R.styleable.GridView_columnWidth, -1);
+ int columnWidth = a.getDimensionPixelOffset(R.styleable.GridView_columnWidth, -1);
if (columnWidth > 0) {
setColumnWidth(columnWidth);
}
- int numColumns = a.getInt(com.android.internal.R.styleable.GridView_numColumns, 1);
+ int numColumns = a.getInt(R.styleable.GridView_numColumns, 1);
setNumColumns(numColumns);
- index = a.getInt(com.android.internal.R.styleable.GridView_gravity, -1);
+ index = a.getInt(R.styleable.GridView_gravity, -1);
if (index >= 0) {
setGravity(index);
}
@@ -2356,6 +2360,36 @@ public class GridView extends AbsListView {
final CollectionInfo collectionInfo = CollectionInfo.obtain(
rowsCount, columnsCount, false, selectionMode);
info.setCollectionInfo(collectionInfo);
+
+ if (columnsCount > 0 || rowsCount > 0) {
+ info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
+ return true;
+ }
+
+ switch (action) {
+ case R.id.accessibilityActionScrollToPosition: {
+ // GridView only supports scrolling in one direction, so we can
+ // ignore the column argument.
+ final int numColumns = getNumColumns();
+ final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1);
+ final int position = Math.min(row * numColumns, getCount() - 1);
+ if (row >= 0) {
+ // The accessibility service gets data asynchronously, so
+ // we'll be a little lenient by clamping the last position.
+ smoothScrollToPosition(position);
+ return true;
+ }
+ } break;
+ }
+
+ return false;
}
@Override
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 041796b..6d2f368 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -687,12 +687,19 @@ public class ImageView extends View {
return mDrawMatrix;
}
+ /**
+ * Adds a transformation {@link Matrix} that is applied
+ * to the view's drawable when it is drawn. Allows custom scaling,
+ * translation, and perspective distortion.
+ *
+ * @param matrix the transformation parameters in matrix form
+ */
public void setImageMatrix(Matrix matrix) {
- // collaps null and identity to just null
+ // collapse null and identity to just null
if (matrix != null && matrix.isIdentity()) {
matrix = null;
}
-
+
// don't invalidate unless we're actually changing our matrix
if (matrix == null && !mMatrix.isIdentity() ||
matrix != null && !mMatrix.equals(matrix)) {
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index ee2c055..def5929 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.os.Bundle;
import android.os.Trace;
import com.android.internal.R;
import com.android.internal.util.Predicate;
@@ -42,6 +43,7 @@ import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
import android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo;
import android.view.accessibility.AccessibilityNodeProvider;
@@ -3893,6 +3895,33 @@ public class ListView extends AbsListView {
final CollectionInfo collectionInfo = CollectionInfo.obtain(
rowsCount, 1, false, selectionMode);
info.setCollectionInfo(collectionInfo);
+
+ if (rowsCount > 0) {
+ info.addAction(AccessibilityAction.ACTION_SCROLL_TO_POSITION);
+ }
+ }
+
+ /** @hide */
+ @Override
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
+ return true;
+ }
+
+ switch (action) {
+ case R.id.accessibilityActionScrollToPosition: {
+ final int row = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_ROW_INT, -1);
+ final int position = Math.min(row, getCount() - 1);
+ if (row >= 0) {
+ // The accessibility service gets data asynchronously, so
+ // we'll be a little lenient by clamping the last position.
+ smoothScrollToPosition(position);
+ return true;
+ }
+ } break;
+ }
+
+ return false;
}
@Override
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 50d701a..205d35e 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -316,7 +316,7 @@ public class ProgressBar extends View {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mProgressTintMode = Drawable.parseTintMode(a.getInt(
- R.styleable.ProgressBar_progressBackgroundTintMode, -1), null);
+ R.styleable.ProgressBar_progressTintMode, -1), null);
mProgressTintInfo.mHasProgressTintMode = true;
}
@@ -334,7 +334,7 @@ public class ProgressBar extends View {
mProgressTintInfo = new ProgressTintInfo();
}
mProgressTintInfo.mProgressBackgroundTintMode = Drawable.parseTintMode(a.getInt(
- R.styleable.ProgressBar_progressTintMode, -1), null);
+ R.styleable.ProgressBar_progressBackgroundTintMode, -1), null);
mProgressTintInfo.mHasProgressBackgroundTintMode = true;
}
@@ -365,7 +365,7 @@ public class ProgressBar extends View {
mProgressTintInfo.mHasSecondaryProgressTint = true;
}
- if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) {
+ if (a.hasValue(R.styleable.ProgressBar_indeterminateTintMode)) {
if (mProgressTintInfo == null) {
mProgressTintInfo = new ProgressTintInfo();
}
@@ -1810,9 +1810,7 @@ public class ProgressBar extends View {
}
if (mRefreshProgressRunnable != null) {
removeCallbacks(mRefreshProgressRunnable);
- }
- if (mRefreshProgressRunnable != null && mRefreshIsPosted) {
- removeCallbacks(mRefreshProgressRunnable);
+ mRefreshIsPosted = false;
}
if (mAccessibilityEventSender != null) {
removeCallbacks(mAccessibilityEventSender);
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 20aa972..9571109 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -79,8 +79,10 @@ public class RadialTimePickerView extends View {
// Transparent alpha level
private static final int ALPHA_TRANSPARENT = 0;
- private static final int DEGREES_FOR_ONE_HOUR = 30;
- private static final int DEGREES_FOR_ONE_MINUTE = 6;
+ private static final int HOURS_IN_CIRCLE = 12;
+ private static final int MINUTES_IN_CIRCLE = 60;
+ private static final int DEGREES_FOR_ONE_HOUR = 360 / HOURS_IN_CIRCLE;
+ private static final int DEGREES_FOR_ONE_MINUTE = 360 / MINUTES_IN_CIRCLE;
private static final int[] HOURS_NUMBERS = {12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
private static final int[] HOURS_NUMBERS_24 = {0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23};
@@ -122,8 +124,9 @@ public class RadialTimePickerView extends View {
private final Paint mPaintCenter = new Paint();
private final Paint[][] mPaintSelector = new Paint[2][3];
- private final int[][] mColorSelector = new int[2][3];
- private final IntHolder[][] mAlphaSelector = new IntHolder[2][3];
+
+ private final int mSelectorColor;
+ private final int mSelectorDotColor;
private final Paint mPaintBackground = new Paint();
@@ -139,14 +142,15 @@ public class RadialTimePickerView extends View {
private final float[] mInnerTextX = new float[12];
private final float[] mInnerTextY = new float[12];
- private final int[] mLineLength = new int[3];
- private final int[] mSelectionDegrees = new int[3];
+ private final int[] mSelectionDegrees = new int[2];
private final ArrayList<Animator> mHoursToMinutesAnims = new ArrayList<>();
private final ArrayList<Animator> mMinuteToHoursAnims = new ArrayList<>();
private final RadialPickerTouchHelper mTouchHelper;
+ private final Path mSelectorPath = new Path();
+
private boolean mIs24HourMode;
private boolean mShowHours;
@@ -165,13 +169,13 @@ public class RadialTimePickerView extends View {
private int mYCenter;
private int mCircleRadius;
- private int mMinHypotenuseForInnerNumber;
- private int mMaxHypotenuseForOuterNumber;
- private int mHalfwayHypotenusePoint;
+ private int mMinDistForInnerNumber;
+ private int mMaxDistForOuterNumber;
+ private int mHalfwayDist;
private String[] mOuterTextHours;
private String[] mInnerTextHours;
- private String[] mOuterTextMinutes;
+ private String[] mMinutesText;
private AnimatorSet mTransition;
private int mAmOrPm;
@@ -316,11 +320,6 @@ public class RadialTimePickerView extends View {
for (int i = 0; i < mAlpha.length; i++) {
mAlpha[i] = new IntHolder(ALPHA_OPAQUE);
}
- for (int i = 0; i < mAlphaSelector.length; i++) {
- for (int j = 0; j < mAlphaSelector[i].length; j++) {
- mAlphaSelector[i][j] = new IntHolder(ALPHA_OPAQUE);
- }
- }
mTextColor[HOURS] = a.getColorStateList(R.styleable.TimePicker_numbersTextColor);
mTextColor[HOURS_INNER] = a.getColorStateList(R.styleable.TimePicker_numbersInnerTextColor);
@@ -345,33 +344,28 @@ public class RadialTimePickerView extends View {
final int[] activatedStateSet = StateSet.get(
StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED);
+ mSelectorColor = selectorActivatedColor;
+ mSelectorDotColor = mTextColor[HOURS].getColorForState(activatedStateSet, 0);
+
mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint();
mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true);
- mColorSelector[HOURS][SELECTOR_CIRCLE] = selectorActivatedColor;
mPaintSelector[HOURS][SELECTOR_DOT] = new Paint();
mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true);
- mColorSelector[HOURS][SELECTOR_DOT] =
- mTextColor[HOURS].getColorForState(activatedStateSet, 0);
mPaintSelector[HOURS][SELECTOR_LINE] = new Paint();
mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true);
mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2);
- mColorSelector[HOURS][SELECTOR_LINE] = selectorActivatedColor;
mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint();
mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true);
- mColorSelector[MINUTES][SELECTOR_CIRCLE] = selectorActivatedColor;
mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint();
mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true);
- mColorSelector[MINUTES][SELECTOR_DOT] =
- mTextColor[MINUTES].getColorForState(activatedStateSet, 0);
mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint();
mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true);
mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2);
- mColorSelector[MINUTES][SELECTOR_LINE] = selectorActivatedColor;
mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
context.getColor(R.color.timepicker_default_numbers_background_color_material)));
@@ -469,11 +463,10 @@ public class RadialTimePickerView extends View {
private void setCurrentHourInternal(int hour, boolean callback, boolean autoAdvance) {
final int degrees = (hour % 12) * DEGREES_FOR_ONE_HOUR;
mSelectionDegrees[HOURS] = degrees;
- mSelectionDegrees[HOURS_INNER] = degrees;
// 0 is 12 AM (midnight) and 12 is 12 PM (noon).
final int amOrPm = (hour == 0 || (hour % 24) < 12) ? AM : PM;
- final boolean isOnInnerCircle = mIs24HourMode && hour >= 1 && hour <= 12;
+ final boolean isOnInnerCircle = getInnerCircleForHour(hour);
if (mAmOrPm != amOrPm || mIsOnInnerCircle != isOnInnerCircle) {
mAmOrPm = amOrPm;
mIsOnInnerCircle = isOnInnerCircle;
@@ -495,8 +488,7 @@ public class RadialTimePickerView extends View {
* @return the current hour between 0 and 23 (inclusive)
*/
public int getCurrentHour() {
- return getHourForDegrees(
- mSelectionDegrees[mIsOnInnerCircle ? HOURS_INNER : HOURS], mIsOnInnerCircle);
+ return getHourForDegrees(mSelectionDegrees[HOURS], mIsOnInnerCircle);
}
private int getHourForDegrees(int degrees, boolean innerCircle) {
@@ -504,11 +496,11 @@ public class RadialTimePickerView extends View {
if (mIs24HourMode) {
// Convert the 12-hour value into 24-hour time based on where the
// selector is positioned.
- if (innerCircle && hour == 0) {
- // Inner circle is 1 through 12.
+ if (!innerCircle && hour == 0) {
+ // Outer circle is 1 through 12.
hour = 12;
- } else if (!innerCircle && hour != 0) {
- // Outer circle is 13 through 23 and 0.
+ } else if (innerCircle && hour != 0) {
+ // Inner circle is 13 through 23 and 0.
hour += 12;
}
} else if (mAmOrPm == PM) {
@@ -517,6 +509,9 @@ public class RadialTimePickerView extends View {
return hour;
}
+ /**
+ * @param hour the hour in 24-hour time or 12-hour time
+ */
private int getDegreesForHour(int hour) {
// Convert to be 0-11.
if (mIs24HourMode) {
@@ -529,12 +524,19 @@ public class RadialTimePickerView extends View {
return hour * DEGREES_FOR_ONE_HOUR;
}
+ /**
+ * @param hour the hour in 24-hour time or 12-hour time
+ */
+ private boolean getInnerCircleForHour(int hour) {
+ return mIs24HourMode && (hour == 0 || hour > 12);
+ }
+
public void setCurrentMinute(int minute) {
setCurrentMinuteInternal(minute, true);
}
private void setCurrentMinuteInternal(int minute, boolean callback) {
- mSelectionDegrees[MINUTES] = (minute % 60) * DEGREES_FOR_ONE_MINUTE;
+ mSelectionDegrees[MINUTES] = (minute % MINUTES_IN_CIRCLE) * DEGREES_FOR_ONE_MINUTE;
invalidate();
@@ -579,6 +581,7 @@ public class RadialTimePickerView extends View {
initData();
invalidate();
+ mTouchHelper.invalidateRoot();
}
public void showMinutes(boolean animate) {
@@ -594,14 +597,15 @@ public class RadialTimePickerView extends View {
initData();
invalidate();
+ mTouchHelper.invalidateRoot();
}
private void initHoursAndMinutesText() {
// Initialize the hours and minutes numbers.
for (int i = 0; i < 12; i++) {
mHours12Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
- mOuterHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]);
- mInnerHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
+ mInnerHours24Texts[i] = String.format("%02d", HOURS_NUMBERS_24[i]);
+ mOuterHours24Texts[i] = String.format("%d", HOURS_NUMBERS[i]);
mMinutesTexts[i] = String.format("%02d", MINUTES_NUMBERS[i]);
}
}
@@ -612,22 +616,16 @@ public class RadialTimePickerView extends View {
mInnerTextHours = mInnerHours24Texts;
} else {
mOuterTextHours = mHours12Texts;
- mInnerTextHours = null;
+ mInnerTextHours = mHours12Texts;
}
- mOuterTextMinutes = mMinutesTexts;
+ mMinutesText = mMinutesTexts;
final int hoursAlpha = mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT;
mAlpha[HOURS].setValue(hoursAlpha);
- mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue(hoursAlpha);
- mAlphaSelector[HOURS][SELECTOR_DOT].setValue(hoursAlpha);
- mAlphaSelector[HOURS][SELECTOR_LINE].setValue(hoursAlpha);
final int minutesAlpha = mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE;
mAlpha[MINUTES].setValue(minutesAlpha);
- mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue(minutesAlpha);
- mAlphaSelector[MINUTES][SELECTOR_DOT].setValue(minutesAlpha);
- mAlphaSelector[MINUTES][SELECTOR_LINE].setValue(minutesAlpha);
}
@Override
@@ -640,9 +638,9 @@ public class RadialTimePickerView extends View {
mYCenter = getHeight() / 2;
mCircleRadius = Math.min(mXCenter, mYCenter);
- mMinHypotenuseForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius;
- mMaxHypotenuseForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius;
- mHalfwayHypotenusePoint = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2;
+ mMinDistForInnerNumber = mCircleRadius - mTextInset[HOURS_INNER] - mSelectorRadius;
+ mMaxDistForOuterNumber = mCircleRadius - mTextInset[HOURS] + mSelectorRadius;
+ mHalfwayDist = mCircleRadius - (mTextInset[HOURS] + mTextInset[HOURS_INNER]) / 2;
calculatePositionsHours();
calculatePositionsMinutes();
@@ -675,7 +673,7 @@ public class RadialTimePickerView extends View {
mOuterTextHours, mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS],
hoursAlpha, !mIsOnInnerCircle, mSelectionDegrees[HOURS], false);
- // Draw inner hours (12-23) for 24-hour time.
+ // Draw inner hours (13-00) for 24-hour time.
if (mIs24HourMode && mInnerTextHours != null) {
drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mTextColor[HOURS_INNER],
mInnerTextHours, mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha,
@@ -687,6 +685,7 @@ public class RadialTimePickerView extends View {
private void drawMinutes(Canvas canvas, float alphaMod) {
final int minutesAlpha = (int) (mAlpha[MINUTES].getValue() * alphaMod + 0.5f);
if (minutesAlpha > 0) {
+ // Draw the minute selector under the elements.
drawSelector(canvas, MINUTES, mSelectorPath, alphaMod);
// Exclude the selector region, then draw minutes with no
@@ -694,7 +693,7 @@ public class RadialTimePickerView extends View {
canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipPath(mSelectorPath, Region.Op.DIFFERENCE);
drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES],
- mOuterTextMinutes, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES],
+ mMinutesText, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES],
minutesAlpha, false, 0, false);
canvas.restore();
@@ -703,7 +702,7 @@ public class RadialTimePickerView extends View {
canvas.save(Canvas.CLIP_SAVE_FLAG);
canvas.clipPath(mSelectorPath, Region.Op.INTERSECT);
drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mTextColor[MINUTES],
- mOuterTextMinutes, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES],
+ mMinutesText, mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES],
minutesAlpha, true, mSelectionDegrees[MINUTES], true);
canvas.restore();
}
@@ -714,69 +713,61 @@ public class RadialTimePickerView extends View {
canvas.drawCircle(mXCenter, mYCenter, mCenterDotRadius, mPaintCenter);
}
+ private int applyAlpha(int argb, int alpha) {
+ final int srcAlpha = (argb >> 24) & 0xFF;
+ final int dstAlpha = (int) (srcAlpha * (alpha / 255.0) + 0.5f);
+ return (0xFFFFFF & argb) | (dstAlpha << 24);
+ }
+
private int getMultipliedAlpha(int argb, int alpha) {
return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
}
- private final Path mSelectorPath = new Path();
-
private void drawSelector(Canvas canvas, int index, Path selectorPath, float alphaMod) {
- // Calculate the current radius at which to place the selection circle.
- mLineLength[index] = mCircleRadius - mTextInset[index];
-
- final double selectionRadians = Math.toRadians(mSelectionDegrees[index]);
-
- float pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians));
- float pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians));
+ final int alpha = (int) (mAlpha[index % 2].getValue() * alphaMod + 0.5f);
+ final int color = applyAlpha(mSelectorColor, alpha);
- int color;
- int alpha;
- Paint paint;
-
- // Draw the selection circle
- color = mColorSelector[index % 2][SELECTOR_CIRCLE];
- alpha = (int) (mAlphaSelector[index % 2][SELECTOR_CIRCLE].getValue() * alphaMod + 0.5f);
- paint = mPaintSelector[index % 2][SELECTOR_CIRCLE];
+ // Calculate the current radius at which to place the selection circle.
+ final int selRadius = mSelectorRadius;
+ final int selLength = mCircleRadius - mTextInset[index];
+ final double selAngleRad = Math.toRadians(mSelectionDegrees[index % 2]);
+ final float selCenterX = mXCenter + selLength * (float) Math.sin(selAngleRad);
+ final float selCenterY = mYCenter - selLength * (float) Math.cos(selAngleRad);
+
+ // Draw the selection circle.
+ final Paint paint = mPaintSelector[index % 2][SELECTOR_CIRCLE];
paint.setColor(color);
- paint.setAlpha(getMultipliedAlpha(color, alpha));
- canvas.drawCircle(pointX, pointY, mSelectorRadius, paint);
+ canvas.drawCircle(selCenterX, selCenterY, selRadius, paint);
// If needed, set up the clip path for later.
if (selectorPath != null) {
- mSelectorPath.reset();
- mSelectorPath.addCircle(pointX, pointY, mSelectorRadius, Path.Direction.CCW);
+ selectorPath.reset();
+ selectorPath.addCircle(selCenterX, selCenterY, selRadius, Path.Direction.CCW);
}
- // Draw the dot if needed.
- final boolean shouldDrawDot = mSelectionDegrees[index] % 30 != 0;
+ // Draw the dot if we're between two items.
+ final boolean shouldDrawDot = mSelectionDegrees[index % 2] % 30 != 0;
if (shouldDrawDot) {
- // We're not on a direct tick
- color = mColorSelector[index % 2][SELECTOR_DOT];
- alpha = (int) (mAlphaSelector[index % 2][SELECTOR_DOT].getValue() * alphaMod + 0.5f);
- paint = mPaintSelector[index % 2][SELECTOR_DOT];
- paint.setColor(color);
- paint.setAlpha(getMultipliedAlpha(color, alpha));
- canvas.drawCircle(pointX, pointY, mSelectorDotRadius, paint);
+ final Paint dotPaint = mPaintSelector[index % 2][SELECTOR_DOT];
+ dotPaint.setColor(mSelectorDotColor);
+ canvas.drawCircle(selCenterX, selCenterY, mSelectorDotRadius, dotPaint);
}
// Shorten the line to only go from the edge of the center dot to the
// edge of the selection circle.
- final double sin = Math.sin(selectionRadians);
- final double cos = Math.cos(selectionRadians);
- final int lineLength = mLineLength[index] - mSelectorRadius;
+ final double sin = Math.sin(selAngleRad);
+ final double cos = Math.cos(selAngleRad);
+ final int lineLength = selLength - selRadius;
final int centerX = mXCenter + (int) (mCenterDotRadius * sin);
final int centerY = mYCenter - (int) (mCenterDotRadius * cos);
- pointX = centerX + (int) (lineLength * sin);
- pointY = centerY - (int) (lineLength * cos);
-
- // Draw the line
- color = mColorSelector[index % 2][SELECTOR_LINE];
- alpha = (int) (mAlphaSelector[index % 2][SELECTOR_LINE].getValue() * alphaMod + 0.5f);
- paint = mPaintSelector[index % 2][SELECTOR_LINE];
- paint.setColor(color);
- paint.setStrokeWidth(mSelectorStroke);
- paint.setAlpha(getMultipliedAlpha(color, alpha));
- canvas.drawLine(mXCenter, mYCenter, pointX, pointY, paint);
+ final float linePointX = centerX + (int) (lineLength * sin);
+ final float linePointY = centerY - (int) (lineLength * cos);
+
+ // Draw the line.
+ final Paint linePaint = mPaintSelector[index % 2][SELECTOR_LINE];
+ linePaint.setColor(color);
+ linePaint.setStrokeWidth(mSelectorStroke);
+ canvas.drawLine(mXCenter, mYCenter, linePointX, linePointY, linePaint);
}
private void calculatePositionsHours() {
@@ -890,21 +881,8 @@ public class RadialTimePickerView extends View {
if (mHoursToMinutesAnims.size() == 0) {
mHoursToMinutesAnims.add(getFadeOutAnimator(mAlpha[HOURS],
ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
- mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
- ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
- mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
- ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
- mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
- ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
-
mHoursToMinutesAnims.add(getFadeInAnimator(mAlpha[MINUTES],
ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
- mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
- ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
- mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
- ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
- mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
- ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
}
if (mTransition != null && mTransition.isRunning()) {
@@ -919,21 +897,8 @@ public class RadialTimePickerView extends View {
if (mMinuteToHoursAnims.size() == 0) {
mMinuteToHoursAnims.add(getFadeOutAnimator(mAlpha[MINUTES],
ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
- mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
- ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
- mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
- ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
- mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
- ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
-
mMinuteToHoursAnims.add(getFadeInAnimator(mAlpha[HOURS],
ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
- mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
- ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
- mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
- ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
- mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
- ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
}
if (mTransition != null && mTransition.isRunning()) {
@@ -945,56 +910,43 @@ public class RadialTimePickerView extends View {
}
private int getDegreesFromXY(float x, float y, boolean constrainOutside) {
- final double hypotenuse = Math.sqrt(
- (y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter));
+ // Ensure the point is inside the touchable area.
+ final int innerBound;
+ final int outerBound;
+ if (mIs24HourMode && mShowHours) {
+ innerBound = mMinDistForInnerNumber;
+ outerBound = mMaxDistForOuterNumber;
+ } else {
+ final int index = mShowHours ? HOURS : MINUTES;
+ final int center = mCircleRadius - mTextInset[index];
+ innerBound = center - mSelectorRadius;
+ outerBound = center + mSelectorRadius;
+ }
- // Basic check if we're outside the range of the disk
- if (constrainOutside && hypotenuse > mCircleRadius) {
+ final double dX = x - mXCenter;
+ final double dY = y - mYCenter;
+ final double distFromCenter = Math.sqrt(dX * dX + dY * dY);
+ if (distFromCenter < innerBound || constrainOutside && distFromCenter > outerBound) {
return -1;
}
- // Check
- if (mIs24HourMode && mShowHours) {
- if (hypotenuse >= mMinHypotenuseForInnerNumber
- && hypotenuse <= mHalfwayHypotenusePoint) {
- mIsOnInnerCircle = true;
- } else if ((hypotenuse <= mMaxHypotenuseForOuterNumber || !constrainOutside)
- && hypotenuse >= mHalfwayHypotenusePoint) {
- mIsOnInnerCircle = false;
- } else {
- return -1;
- }
+ // Convert to degrees.
+ final int degrees = (int) (Math.toDegrees(Math.atan2(dY, dX) + Math.PI / 2) + 0.5);
+ if (degrees < 0) {
+ return degrees + 360;
} else {
- final int index = (mShowHours) ? HOURS : MINUTES;
- final float length = (mCircleRadius - mTextInset[index]);
- final int distanceToNumber = (int) (hypotenuse - length);
- final int maxAllowedDistance = mTextInset[index];
- if (distanceToNumber < -maxAllowedDistance
- || (constrainOutside && distanceToNumber > maxAllowedDistance)) {
- return -1;
- }
+ return degrees;
}
+ }
- final float opposite = Math.abs(y - mYCenter);
- int degrees = (int) (Math.toDegrees(Math.asin(opposite / hypotenuse)) + 0.5);
-
- // Now we have to translate to the correct quadrant.
- final boolean rightSide = (x > mXCenter);
- final boolean topSide = (y < mYCenter);
- if (rightSide) {
- if (topSide) {
- degrees = 90 - degrees;
- } else {
- degrees = 90 + degrees;
- }
- } else {
- if (topSide) {
- degrees = 270 + degrees;
- } else {
- degrees = 270 - degrees;
- }
+ private boolean getInnerCircleFromXY(float x, float y) {
+ if (mIs24HourMode && mShowHours) {
+ final double dX = x - mXCenter;
+ final double dY = y - mYCenter;
+ final double distFromCenter = Math.sqrt(dX * dX + dY * dY);
+ return distFromCenter <= mHalfwayDist;
}
- return degrees;
+ return false;
}
boolean mChangedDuringTouch = false;
@@ -1034,34 +986,28 @@ public class RadialTimePickerView extends View {
private boolean handleTouchInput(
float x, float y, boolean forceSelection, boolean autoAdvance) {
- // Calling getDegreesFromXY has side effects, so cache
- // whether we used to be on the inner circle.
- final boolean wasOnInnerCircle = mIsOnInnerCircle;
+ final boolean isOnInnerCircle = getInnerCircleFromXY(x, y);
final int degrees = getDegreesFromXY(x, y, false);
if (degrees == -1) {
return false;
}
- final int[] selectionDegrees = mSelectionDegrees;
final int type;
final int newValue;
final boolean valueChanged;
if (mShowHours) {
final int snapDegrees = snapOnly30s(degrees, 0) % 360;
- valueChanged = selectionDegrees[HOURS] != snapDegrees
- || selectionDegrees[HOURS_INNER] != snapDegrees
- || wasOnInnerCircle != mIsOnInnerCircle;
-
- selectionDegrees[HOURS] = snapDegrees;
- selectionDegrees[HOURS_INNER] = snapDegrees;
+ valueChanged = mIsOnInnerCircle != isOnInnerCircle
+ || mSelectionDegrees[HOURS] != snapDegrees;
+ mIsOnInnerCircle = isOnInnerCircle;
+ mSelectionDegrees[HOURS] = snapDegrees;
type = HOURS;
newValue = getCurrentHour();
} else {
final int snapDegrees = snapPrefer30s(degrees) % 360;
- valueChanged = selectionDegrees[MINUTES] != snapDegrees;
-
- selectionDegrees[MINUTES] = snapDegrees;
+ valueChanged = mSelectionDegrees[MINUTES] != snapDegrees;
+ mSelectionDegrees[MINUTES] = snapDegrees;
type = MINUTES;
newValue = getCurrentMinute();
}
@@ -1179,17 +1125,11 @@ public class RadialTimePickerView extends View {
@Override
protected int getVirtualViewAt(float x, float y) {
final int id;
-
- // Calling getDegreesXY() has side-effects, so we need to cache the
- // current inner circle value and restore after the call.
- final boolean wasOnInnerCircle = mIsOnInnerCircle;
final int degrees = getDegreesFromXY(x, y, true);
- final boolean isOnInnerCircle = mIsOnInnerCircle;
- mIsOnInnerCircle = wasOnInnerCircle;
-
if (degrees != -1) {
final int snapDegrees = snapOnly30s(degrees, 0) % 360;
if (mShowHours) {
+ final boolean isOnInnerCircle = getInnerCircleFromXY(x, y);
final int hour24 = getHourForDegrees(snapDegrees, isOnInnerCircle);
final int hour = mIs24HourMode ? hour24 : hour24To12(hour24);
id = makeId(TYPE_HOUR, hour);
@@ -1200,8 +1140,10 @@ public class RadialTimePickerView extends View {
// If the touched minute is closer to the current minute
// than it is to the snapped minute, return current.
+ final int currentOffset = getCircularDiff(current, touched, MINUTES_IN_CIRCLE);
+ final int snappedOffset = getCircularDiff(snapped, touched, MINUTES_IN_CIRCLE);
final int minute;
- if (Math.abs(current - touched) < Math.abs(snapped - touched)) {
+ if (currentOffset < snappedOffset) {
minute = current;
} else {
minute = snapped;
@@ -1215,6 +1157,20 @@ public class RadialTimePickerView extends View {
return id;
}
+ /**
+ * Returns the difference in degrees between two values along a circle.
+ *
+ * @param first value in the range [0,max]
+ * @param second value in the range [0,max]
+ * @param max the maximum value along the circle
+ * @return the difference in between the two values
+ */
+ private int getCircularDiff(int first, int second, int max) {
+ final int diff = Math.abs(first - second);
+ final int midpoint = max / 2;
+ return (diff > midpoint) ? (max - diff) : diff;
+ }
+
@Override
protected void getVisibleVirtualViews(IntArray virtualViewIds) {
if (mShowHours) {
@@ -1225,7 +1181,7 @@ public class RadialTimePickerView extends View {
}
} else {
final int current = getCurrentMinute();
- for (int i = 0; i < 60; i += MINUTE_INCREMENT) {
+ for (int i = 0; i < MINUTES_IN_CIRCLE; i += MINUTE_INCREMENT) {
virtualViewIds.add(makeId(TYPE_MINUTE, i));
// If the current minute falls between two increments,
@@ -1283,7 +1239,7 @@ public class RadialTimePickerView extends View {
if (value < current && nextValue > current) {
// The current value is between two snap values.
return makeId(type, current);
- } else if (nextValue < 60) {
+ } else if (nextValue < MINUTES_IN_CIRCLE) {
return makeId(type, nextValue);
}
}
@@ -1337,7 +1293,7 @@ public class RadialTimePickerView extends View {
final float centerRadius;
final float degrees;
if (type == TYPE_HOUR) {
- final boolean innerCircle = mIs24HourMode && value > 0 && value <= 12;
+ final boolean innerCircle = getInnerCircleForHour(value);
if (innerCircle) {
centerRadius = mCircleRadius - mTextInset[HOURS_INNER];
radius = mSelectorRadius;
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 349f3f0..a50941b 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -815,12 +815,12 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback
mContext = context;
mIntent = intent;
- mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
-
- mLayoutInflater = LayoutInflater.from(context);
if (mIntent == null) {
throw new IllegalArgumentException("Non-null Intent must be specified.");
}
+
+ mAppWidgetId = intent.getIntExtra(RemoteViews.EXTRA_REMOTEADAPTER_APPWIDGET_ID, -1);
+ mLayoutInflater = LayoutInflater.from(context);
mRequestedViews = new RemoteViewsFrameLayoutRefSet();
// Strip the previously injected app widget id from service intent
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 4e5a39a..d9f1f0e 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -26,6 +26,7 @@ import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextPaint;
import android.text.format.DateFormat;
@@ -59,6 +60,12 @@ class SimpleMonthView extends View {
private static final String DEFAULT_TITLE_FORMAT = "MMMMy";
private static final String DAY_OF_WEEK_FORMAT = "EEEEE";
+ /** Virtual view ID for previous button. */
+ private static final int ITEM_ID_PREV = 0x101;
+
+ /** Virtual view ID for next button. */
+ private static final int ITEM_ID_NEXT = 0x100;
+
private final TextPaint mMonthPaint = new TextPaint();
private final TextPaint mDayOfWeekPaint = new TextPaint();
private final TextPaint mDayPaint = new TextPaint();
@@ -66,27 +73,43 @@ class SimpleMonthView extends View {
private final Paint mDayHighlightPaint = new Paint();
private final Calendar mCalendar = Calendar.getInstance();
- private final Calendar mDayLabelCalendar = Calendar.getInstance();
+ private final Calendar mDayOfWeekLabelCalendar = Calendar.getInstance();
private final MonthViewTouchHelper mTouchHelper;
private final SimpleDateFormat mTitleFormatter;
private final SimpleDateFormat mDayOfWeekFormatter;
+ // Desired dimensions.
+ private final int mDesiredMonthHeight;
+ private final int mDesiredDayOfWeekHeight;
+ private final int mDesiredDayHeight;
+ private final int mDesiredCellWidth;
+ private final int mDesiredDaySelectorRadius;
+
+ // Next/previous drawables.
+ private final Drawable mPrevDrawable;
+ private final Drawable mNextDrawable;
+ private final Rect mPrevHitArea;
+ private final Rect mNextHitArea;
+ private final CharSequence mPrevContentDesc;
+ private final CharSequence mNextContentDesc;
+
private CharSequence mTitle;
private int mMonth;
private int mYear;
+ // Dimensions as laid out.
+ private int mMonthHeight;
+ private int mDayOfWeekHeight;
+ private int mDayHeight;
+ private int mCellWidth;
+ private int mDaySelectorRadius;
+
private int mPaddedWidth;
private int mPaddedHeight;
- private final int mMonthHeight;
- private final int mDayOfWeekHeight;
- private final int mDayHeight;
- private final int mCellWidth;
- private final int mDaySelectorRadius;
-
/** The day of month for the selected day, or -1 if no day is selected. */
private int mActivatedDay = -1;
@@ -122,7 +145,10 @@ class SimpleMonthView extends View {
private ColorStateList mDayTextColor;
- private int mTouchedDay = -1;
+ private int mTouchedItem = -1;
+
+ private boolean mPrevEnabled;
+ private boolean mNextEnabled;
public SimpleMonthView(Context context) {
this(context, null);
@@ -140,11 +166,18 @@ class SimpleMonthView extends View {
super(context, attrs, defStyleAttr, defStyleRes);
final Resources res = context.getResources();
- mMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height);
- mDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height);
- mDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height);
- mCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width);
- mDaySelectorRadius = res.getDimensionPixelSize(R.dimen.date_picker_day_selector_radius);
+ mDesiredMonthHeight = res.getDimensionPixelSize(R.dimen.date_picker_month_height);
+ mDesiredDayOfWeekHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_of_week_height);
+ mDesiredDayHeight = res.getDimensionPixelSize(R.dimen.date_picker_day_height);
+ mDesiredCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width);
+ mDesiredDaySelectorRadius = res.getDimensionPixelSize(R.dimen.date_picker_day_selector_radius);
+
+ mPrevDrawable = context.getDrawable(R.drawable.ic_chevron_left);
+ mNextDrawable = context.getDrawable(R.drawable.ic_chevron_right);
+ mPrevHitArea = mPrevDrawable != null ? new Rect() : null;
+ mNextHitArea = mNextDrawable != null ? new Rect() : null;
+ mPrevContentDesc = res.getText(R.string.date_picker_prev_month_button);
+ mNextContentDesc = res.getText(R.string.date_picker_next_month_button);
// Set up accessibility components.
mTouchHelper = new MonthViewTouchHelper(this);
@@ -160,6 +193,18 @@ class SimpleMonthView extends View {
initPaints(res);
}
+ public void setNextEnabled(boolean enabled) {
+ mNextEnabled = enabled;
+ mTouchHelper.invalidateRoot();
+ invalidate();
+ }
+
+ public void setPrevEnabled(boolean enabled) {
+ mPrevEnabled = enabled;
+ mTouchHelper.invalidateRoot();
+ invalidate();
+ }
+
/**
* Applies the specified text appearance resource to a paint, returning the
* text color if one is set in the text appearance.
@@ -192,7 +237,16 @@ class SimpleMonthView extends View {
}
public void setMonthTextAppearance(int resId) {
- applyTextAppearance(mMonthPaint, resId);
+ final ColorStateList monthColor = applyTextAppearance(mMonthPaint, resId);
+ if (monthColor != null) {
+ if (mPrevDrawable != null) {
+ mPrevDrawable.setTintList(monthColor);
+ }
+ if (mNextDrawable != null) {
+ mNextDrawable.setTintList(monthColor);
+ }
+ }
+
invalidate();
}
@@ -300,23 +354,26 @@ class SimpleMonthView extends View {
@Override
public boolean onTouchEvent(MotionEvent event) {
+ final int x = (int) (event.getX() + 0.5f);
+ final int y = (int) (event.getY() + 0.5f);
+
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
- final int touchedDay = getDayAtLocation(event.getX(), event.getY());
- if (mTouchedDay != touchedDay) {
- mTouchedDay = touchedDay;
+ final int touchedItem = getItemAtLocation(x, y);
+ if (mTouchedItem != touchedItem) {
+ mTouchedItem = touchedItem;
invalidate();
}
break;
case MotionEvent.ACTION_UP:
- final int clickedDay = getDayAtLocation(event.getX(), event.getY());
- onDayClicked(clickedDay);
+ final int clickedItem = getItemAtLocation(x, y);
+ onItemClicked(clickedItem, true);
// Fall through.
case MotionEvent.ACTION_CANCEL:
// Reset touched day on stream end.
- mTouchedDay = -1;
+ mTouchedItem = -1;
invalidate();
break;
}
@@ -332,6 +389,7 @@ class SimpleMonthView extends View {
drawMonth(canvas);
drawDaysOfWeek(canvas);
drawDays(canvas);
+ drawButtons(canvas);
canvas.translate(-paddingLeft, -paddingTop);
}
@@ -347,34 +405,43 @@ class SimpleMonthView extends View {
}
private void drawDaysOfWeek(Canvas canvas) {
- final float cellWidthHalf = mPaddedWidth / (DAYS_IN_WEEK * 2);
-
- // Vertically centered within the cell height.
- final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent();
- final float y = mMonthHeight + (mDayOfWeekHeight - lineHeight) / 2f;
-
- for (int i = 0; i < DAYS_IN_WEEK; i++) {
- final int calendarDay = (i + mWeekStart) % DAYS_IN_WEEK;
- mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
-
- final String dayLabel = mDayOfWeekFormatter.format(mDayLabelCalendar.getTime());
- final float x = (2 * i + 1) * cellWidthHalf;
- canvas.drawText(dayLabel, x, y, mDayOfWeekPaint);
+ final TextPaint p = mDayOfWeekPaint;
+ final int headerHeight = mMonthHeight;
+ final int rowHeight = mDayOfWeekHeight;
+ final int colWidth = mCellWidth;
+
+ // Text is vertically centered within the day of week height.
+ final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
+ final int rowCenter = headerHeight + rowHeight / 2;
+
+ for (int col = 0; col < DAYS_IN_WEEK; col++) {
+ final int colCenter = colWidth * col + colWidth / 2;
+ final int dayOfWeek = (col + mWeekStart) % DAYS_IN_WEEK;
+ final String label = getDayOfWeekLabel(dayOfWeek);
+ canvas.drawText(label, colCenter, rowCenter - halfLineHeight, p);
}
}
+ private String getDayOfWeekLabel(int dayOfWeek) {
+ mDayOfWeekLabelCalendar.set(Calendar.DAY_OF_WEEK, dayOfWeek);
+ return mDayOfWeekFormatter.format(mDayOfWeekLabelCalendar.getTime());
+ }
+
/**
* Draws the month days.
*/
private void drawDays(Canvas canvas) {
- final int cellWidthHalf = mPaddedWidth / (DAYS_IN_WEEK * 2);
+ final TextPaint p = mDayPaint;
+ final int headerHeight = mMonthHeight + mDayOfWeekHeight;
+ final int rowHeight = mDayHeight;
+ final int colWidth = mCellWidth;
- // Vertically centered within the cell height.
- final float halfLineHeight = (mDayPaint.ascent() + mDayPaint.descent()) / 2;
- float centerY = mMonthHeight + mDayOfWeekHeight + mDayHeight / 2f;
+ // Text is vertically centered within the row height.
+ final float halfLineHeight = (p.ascent() + p.descent()) / 2f;
+ int rowCenter = headerHeight + rowHeight / 2;
- for (int day = 1, j = findDayOffset(); day <= mDaysInMonth; day++) {
- final int x = (2 * j + 1) * cellWidthHalf;
+ for (int day = 1, col = findDayOffset(); day <= mDaysInMonth; day++) {
+ final int colCenter = colWidth * col + colWidth / 2;
int stateMask = 0;
if (day >= mEnabledDayStart && day <= mEnabledDayEnd) {
@@ -386,12 +453,12 @@ class SimpleMonthView extends View {
stateMask |= StateSet.VIEW_STATE_ACTIVATED;
// Adjust the circle to be centered on the row.
- canvas.drawCircle(x, centerY, mDaySelectorRadius, mDaySelectorPaint);
- } else if (mTouchedDay == day) {
+ canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDaySelectorPaint);
+ } else if (mTouchedItem == day) {
stateMask |= StateSet.VIEW_STATE_PRESSED;
// Adjust the circle to be centered on the row.
- canvas.drawCircle(x, centerY, mDaySelectorRadius, mDayHighlightPaint);
+ canvas.drawCircle(colCenter, rowCenter, mDaySelectorRadius, mDayHighlightPaint);
}
final boolean isDayToday = mToday == day;
@@ -402,19 +469,29 @@ class SimpleMonthView extends View {
final int[] stateSet = StateSet.get(stateMask);
dayTextColor = mDayTextColor.getColorForState(stateSet, 0);
}
- mDayPaint.setColor(dayTextColor);
+ p.setColor(dayTextColor);
- canvas.drawText("" + day, x, centerY - halfLineHeight, mDayPaint);
+ canvas.drawText(Integer.toString(day), colCenter, rowCenter - halfLineHeight, p);
- j++;
+ col++;
- if (j == DAYS_IN_WEEK) {
- j = 0;
- centerY += mDayHeight;
+ if (col == DAYS_IN_WEEK) {
+ col = 0;
+ rowCenter += rowHeight;
}
}
}
+ private void drawButtons(Canvas canvas) {
+ if (mPrevEnabled && mPrevDrawable != null) {
+ mPrevDrawable.draw(canvas);
+ }
+
+ if (mNextEnabled && mNextDrawable != null) {
+ mNextDrawable.draw(canvas);
+ }
+ }
+
private static boolean isValidDayOfWeek(int day) {
return day >= Calendar.SUNDAY && day <= Calendar.SATURDAY;
}
@@ -558,9 +635,9 @@ class SimpleMonthView extends View {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int preferredHeight = mDayHeight * mNumWeeks + mDayOfWeekHeight + mMonthHeight
- + getPaddingTop() + getPaddingBottom();
- final int preferredWidth = mCellWidth * DAYS_IN_WEEK
+ final int preferredHeight = mDesiredDayHeight * mNumWeeks + mDesiredDayOfWeekHeight
+ + mDesiredMonthHeight + getPaddingTop() + getPaddingBottom();
+ final int preferredWidth = mDesiredCellWidth * DAYS_IN_WEEK
+ getPaddingStart() + getPaddingEnd();
final int resolvedWidth = resolveSize(preferredWidth, widthMeasureSpec);
final int resolvedHeight = resolveSize(preferredHeight, heightMeasureSpec);
@@ -568,9 +645,73 @@ class SimpleMonthView extends View {
}
@Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mPaddedWidth = w - getPaddingLeft() - getPaddingRight();
- mPaddedHeight = w - getPaddingTop() - getPaddingBottom();
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (!changed) {
+ return;
+ }
+
+ // Let's initialize a completely reasonable number of variables.
+ final int w = right - left;
+ final int h = bottom - top;
+ final int paddingLeft = getPaddingLeft();
+ final int paddingTop = getPaddingTop();
+ final int paddingRight = getPaddingRight();
+ final int paddingBottom = getPaddingBottom();
+ final int paddedRight = w - paddingRight;
+ final int paddedBottom = h - paddingBottom;
+ final int paddedWidth = paddedRight - paddingLeft;
+ final int paddedHeight = paddedBottom - paddingTop;
+ if (paddedWidth == mPaddedWidth || paddedHeight == mPaddedHeight) {
+ return;
+ }
+
+ mPaddedWidth = paddedWidth;
+ mPaddedHeight = paddedHeight;
+
+ // We may have been laid out smaller than our preferred size. If so,
+ // scale all dimensions to fit.
+ final int measuredPaddedHeight = getMeasuredHeight() - paddingTop - paddingBottom;
+ final float scaleH = paddedHeight / (float) measuredPaddedHeight;
+ final int monthHeight = (int) (mDesiredMonthHeight * scaleH);
+ final int cellWidth = mPaddedWidth / DAYS_IN_WEEK;
+ mMonthHeight = monthHeight;
+ mDayOfWeekHeight = (int) (mDesiredDayOfWeekHeight * scaleH);
+ mDayHeight = (int) (mDesiredDayHeight * scaleH);
+ mCellWidth = cellWidth;
+
+ // Compute the largest day selector radius that's still within the clip
+ // bounds and desired selector radius.
+ final int maxSelectorWidth = cellWidth / 2 + Math.min(paddingLeft, paddingRight);
+ final int maxSelectorHeight = mDayHeight / 2 + paddingBottom;
+ mDaySelectorRadius = Math.min(mDesiredDaySelectorRadius,
+ Math.min(maxSelectorWidth, maxSelectorHeight));
+
+ // Vertically center the previous/next drawables within the month
+ // header, horizontally center within the day cell, then expand the
+ // hit area to ensure it's at least 48x48dp.
+ final Drawable prevDrawable = mPrevDrawable;
+ if (prevDrawable != null) {
+ final int dW = prevDrawable.getIntrinsicWidth();
+ final int dH = prevDrawable.getIntrinsicHeight();
+ final int iconTop = (monthHeight - dH) / 2;
+ final int iconLeft = (cellWidth - dW) / 2;
+
+ // Button bounds don't include padding, but hit area does.
+ prevDrawable.setBounds(iconLeft, iconTop, iconLeft + dW, iconTop + dH);
+ mPrevHitArea.set(0, 0, paddingLeft + cellWidth, paddingTop + monthHeight);
+ }
+
+ final Drawable nextDrawable = mNextDrawable;
+ if (nextDrawable != null) {
+ final int dW = nextDrawable.getIntrinsicWidth();
+ final int dH = nextDrawable.getIntrinsicHeight();
+ final int iconTop = (monthHeight - dH) / 2;
+ final int iconRight = paddedWidth - (cellWidth - dW) / 2;
+
+ // Button bounds don't include padding, but hit area does.
+ nextDrawable.setBounds(iconRight - dW, iconTop, iconRight, iconTop + dH);
+ mNextHitArea.set(paddedRight - cellWidth, 0, w, paddingTop + monthHeight);
+ }
// Invalidate cached accessibility information.
mTouchHelper.invalidateRoot();
@@ -585,22 +726,29 @@ class SimpleMonthView extends View {
}
/**
- * Calculates the day of the month at the specified touch position. Returns
- * the day of the month or -1 if the position wasn't in a valid day.
+ * Calculates the day of the month or item identifier at the specified
+ * touch position. Returns the day of the month or -1 if the position
+ * wasn't in a valid day.
*
* @param x the x position of the touch event
* @param y the y position of the touch event
- * @return the day of the month at (x, y) or -1 if the position wasn't in a
- * valid day
+ * @return the day of the month at (x, y), an item identifier, or -1 if the
+ * position wasn't in a valid day or item
*/
- private int getDayAtLocation(float x, float y) {
- final int paddedX = (int) (x - getPaddingLeft() + 0.5f);
+ private int getItemAtLocation(int x, int y) {
+ if (mNextEnabled && mNextDrawable != null && mNextHitArea.contains(x, y)) {
+ return ITEM_ID_NEXT;
+ } else if (mPrevEnabled && mPrevDrawable != null && mPrevHitArea.contains(x, y)) {
+ return ITEM_ID_PREV;
+ }
+
+ final int paddedX = x - getPaddingLeft();
if (paddedX < 0 || paddedX >= mPaddedWidth) {
return -1;
}
final int headerHeight = mMonthHeight + mDayOfWeekHeight;
- final int paddedY = (int) (y - getPaddingTop() + 0.5f);
+ final int paddedY = y - getPaddingTop();
if (paddedY < headerHeight || paddedY >= mPaddedHeight) {
return -1;
}
@@ -619,47 +767,97 @@ class SimpleMonthView extends View {
/**
* Calculates the bounds of the specified day.
*
- * @param day the day of the month
+ * @param id the day of the month, or an item identifier
* @param outBounds the rect to populate with bounds
*/
- private boolean getBoundsForDay(int day, Rect outBounds) {
- if (day < 1 || day > mDaysInMonth) {
+ private boolean getBoundsForItem(int id, Rect outBounds) {
+ if (mNextEnabled && id == ITEM_ID_NEXT) {
+ if (mNextDrawable != null) {
+ outBounds.set(mNextHitArea);
+ return true;
+ }
+ } else if (mPrevEnabled && id == ITEM_ID_PREV) {
+ if (mPrevDrawable != null) {
+ outBounds.set(mPrevHitArea);
+ return true;
+ }
+ }
+
+ if (id < 1 || id > mDaysInMonth) {
return false;
}
- final int index = day - 1 + findDayOffset();
- final int row = index / DAYS_IN_WEEK;
+ final int index = id - 1 + findDayOffset();
+
+ // Compute left edge.
final int col = index % DAYS_IN_WEEK;
+ final int colWidth = mCellWidth;
+ final int left = getPaddingLeft() + col * colWidth;
+ // Compute top edge.
+ final int row = index / DAYS_IN_WEEK;
+ final int rowHeight = mDayHeight;
final int headerHeight = mMonthHeight + mDayOfWeekHeight;
- final int paddedY = row * mDayHeight + headerHeight;
- final int paddedX = col * mPaddedWidth;
-
- final int y = paddedY + getPaddingTop();
- final int x = paddedX + getPaddingLeft();
-
- final int cellHeight = mDayHeight;
- final int cellWidth = mPaddedWidth / DAYS_IN_WEEK;
- outBounds.set(x, y, (x + cellWidth), (y + cellHeight));
+ final int top = getPaddingTop() + headerHeight + row * rowHeight;
+ outBounds.set(left, top, left + colWidth, top + rowHeight);
return true;
}
/**
+ * Called when an item is clicked.
+ *
+ * @param id the day number or item identifier
+ */
+ private boolean onItemClicked(int id, boolean animate) {
+ return onNavigationClicked(id, animate) || onDayClicked(id);
+ }
+
+ /**
* Called when the user clicks on a day. Handles callbacks to the
* {@link OnDayClickListener} if one is set.
*
* @param day the day that was clicked
*/
- private void onDayClicked(int day) {
+ private boolean onDayClicked(int day) {
+ if (day < 0 || day > mDaysInMonth) {
+ return false;
+ }
+
if (mOnDayClickListener != null) {
- Calendar date = Calendar.getInstance();
+ final Calendar date = Calendar.getInstance();
date.set(mYear, mMonth, day);
mOnDayClickListener.onDayClick(this, date);
}
// This is a no-op if accessibility is turned off.
mTouchHelper.sendEventForVirtualView(day, AccessibilityEvent.TYPE_VIEW_CLICKED);
+ return true;
+ }
+
+ /**
+ * Called when the user clicks on a navigation button. Handles callbacks to
+ * the {@link OnDayClickListener} if one is set.
+ *
+ * @param id the item identifier
+ */
+ private boolean onNavigationClicked(int id, boolean animate) {
+ final int direction;
+ if (id == ITEM_ID_NEXT) {
+ direction = 1;
+ } else if (id == ITEM_ID_PREV) {
+ direction = -1;
+ } else {
+ return false;
+ }
+
+ if (mOnDayClickListener != null) {
+ mOnDayClickListener.onNavigationClick(this, direction, animate);
+ }
+
+ // This is a no-op if accessibility is turned off.
+ mTouchHelper.sendEventForVirtualView(id, AccessibilityEvent.TYPE_VIEW_CLICKED);
+ return true;
}
/**
@@ -678,7 +876,7 @@ class SimpleMonthView extends View {
@Override
protected int getVirtualViewAt(float x, float y) {
- final int day = getDayAtLocation(x, y);
+ final int day = getItemAtLocation((int) (x + 0.5f), (int) (y + 0.5f));
if (day >= 0) {
return day;
}
@@ -687,6 +885,14 @@ class SimpleMonthView extends View {
@Override
protected void getVisibleVirtualViews(IntArray virtualViewIds) {
+ if (mNextEnabled && mNextDrawable != null) {
+ virtualViewIds.add(ITEM_ID_PREV);
+ }
+
+ if (mPrevEnabled && mPrevDrawable != null) {
+ virtualViewIds.add(ITEM_ID_NEXT);
+ }
+
for (int day = 1; day <= mDaysInMonth; day++) {
virtualViewIds.add(day);
}
@@ -699,7 +905,7 @@ class SimpleMonthView extends View {
@Override
protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) {
- final boolean hasBounds = getBoundsForDay(virtualViewId, mTempRect);
+ final boolean hasBounds = getBoundsForItem(virtualViewId, mTempRect);
if (!hasBounds) {
// The day is invalid, kill the node.
@@ -710,12 +916,14 @@ class SimpleMonthView extends View {
return;
}
+ node.setText(getItemText(virtualViewId));
node.setContentDescription(getItemDescription(virtualViewId));
node.setBoundsInParent(mTempRect);
node.addAction(AccessibilityAction.ACTION_CLICK);
if (virtualViewId == mActivatedDay) {
- node.setSelected(true);
+ // TODO: This should use activated once that's supported.
+ node.setChecked(true);
}
}
@@ -725,31 +933,45 @@ class SimpleMonthView extends View {
Bundle arguments) {
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK:
- onDayClicked(virtualViewId);
- return true;
+ return onItemClicked(virtualViewId, false);
}
return false;
}
/**
- * Generates a description for a given time object. Since this
- * description will be spoken, the components are ordered by descending
- * specificity as DAY MONTH YEAR.
+ * Generates a description for a given virtual view.
*
- * @param day The day to generate a description for
- * @return A description of the time object
+ * @param id the day or item identifier to generate a description for
+ * @return a description of the virtual view
*/
- private CharSequence getItemDescription(int day) {
- mTempCalendar.set(mYear, mMonth, day);
- final CharSequence date = DateFormat.format(DATE_FORMAT,
- mTempCalendar.getTimeInMillis());
+ private CharSequence getItemDescription(int id) {
+ if (id == ITEM_ID_NEXT) {
+ return mNextContentDesc;
+ } else if (id == ITEM_ID_PREV) {
+ return mPrevContentDesc;
+ } else if (id >= 1 && id <= mDaysInMonth) {
+ mTempCalendar.set(mYear, mMonth, id);
+ return DateFormat.format(DATE_FORMAT, mTempCalendar.getTimeInMillis());
+ }
- if (day == mActivatedDay) {
- return getContext().getString(R.string.item_is_selected, date);
+ return "";
+ }
+
+ /**
+ * Generates displayed text for a given virtual view.
+ *
+ * @param id the day or item identifier to generate text for
+ * @return the visible text of the virtual view
+ */
+ private CharSequence getItemText(int id) {
+ if (id == ITEM_ID_NEXT || id == ITEM_ID_PREV) {
+ return null;
+ } else if (id >= 1 && id <= mDaysInMonth) {
+ return Integer.toString(id);
}
- return date;
+ return null;
}
}
@@ -758,5 +980,6 @@ class SimpleMonthView extends View {
*/
public interface OnDayClickListener {
public void onDayClick(SimpleMonthView view, Calendar day);
+ public void onNavigationClick(SimpleMonthView view, int direction, boolean animate);
}
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index bb290e7..ae779fe 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -1363,8 +1363,8 @@ public class Switch extends CompoundButton {
}
@Override
- public void onProvideAssistStructure(ViewAssistStructure structure, Bundle extras) {
- super.onProvideAssistStructure(structure, extras);
+ public void onProvideAssistStructure(ViewAssistStructure structure) {
+ super.onProvideAssistStructure(structure);
CharSequence switchText = isChecked() ? mTextOn : mTextOff;
if (!TextUtils.isEmpty(switchText)) {
CharSequence oldText = structure.getText();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 718ef93..9caa584 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -543,6 +543,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private float mSpacingMult = 1.0f;
private float mSpacingAdd = 0.0f;
+ private int mBreakStrategy;
+
private int mMaximum = Integer.MAX_VALUE;
private int mMaxMode = LINES;
private int mMinimum = 0;
@@ -680,6 +682,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean elegant = false;
float letterSpacing = 0;
String fontFeatureSettings = null;
+ mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
final Resources.Theme theme = context.getTheme();
@@ -1133,6 +1136,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextView_fontFeatureSettings:
fontFeatureSettings = a.getString(attr);
break;
+
+ case com.android.internal.R.styleable.TextView_breakStrategy:
+ mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
}
}
a.recycle();
@@ -2960,6 +2966,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Sets the break strategy for breaking paragraphs into lines. The default value for
+ * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
+ * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
+ * text "dancing" when being edited.
+ *
+ * @attr ref android.R.styleable#TextView_breakStrategy
+ * @see #getBreakStrategy()
+ */
+ public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
+ mBreakStrategy = breakStrategy;
+ if (mLayout != null) {
+ nullLayouts();
+ requestLayout();
+ invalidate();
+ }
+ }
+
+ /**
+ * @return the currently set break strategy.
+ *
+ * @attr ref android.R.styleable#TextView_breakStrategy
+ * @see #setBreakStrategy(int)
+ */
+ @Layout.BreakStrategy
+ public int getBreakStrategy() {
+ return mBreakStrategy;
+ }
+
+ /**
* Sets font feature settings. The format is the same as the CSS
* font-feature-settings attribute:
* http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
@@ -6492,27 +6527,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
hintBoring, mIncludePad, mEllipsize,
ellipsisWidth);
}
- } else if (shouldEllipsize) {
- mHintLayout = new StaticLayout(mHint,
- 0, mHint.length(),
- mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
- mSpacingAdd, mIncludePad, mEllipsize,
- ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
- } else {
- mHintLayout = new StaticLayout(mHint, mTextPaint,
- hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
- mIncludePad);
}
- } else if (shouldEllipsize) {
- mHintLayout = new StaticLayout(mHint,
- 0, mHint.length(),
- mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
- mSpacingAdd, mIncludePad, mEllipsize,
- ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
- } else {
- mHintLayout = new StaticLayout(mHint, mTextPaint,
- hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
- mIncludePad);
+ }
+ // TODO: code duplication with makeSingleLayout()
+ if (mHintLayout == null) {
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
+ mHint.length(), hintWidth)
+ .setPaint(mTextPaint)
+ .setAlignment(alignment)
+ .setTextDir(mTextDir)
+ .setSpacingMult(mSpacingMult)
+ .setSpacingAdd(mSpacingAdd)
+ .setIncludePad(mIncludePad)
+ .setBreakStrategy(mBreakStrategy);
+ if (shouldEllipsize) {
+ builder.setEllipsize(mEllipsize)
+ .setEllipsizedWidth(ellipsisWidth)
+ .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
+ }
+ mHintLayout = builder.build();
}
}
@@ -6544,9 +6577,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Layout result = null;
if (mText instanceof Spannable) {
result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
- alignment, mTextDir, mSpacingMult,
- mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
- ellipsisWidth);
+ alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mBreakStrategy,
+ getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
} else {
if (boring == UNKNOWN_BORING) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
@@ -6583,29 +6615,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boring, mIncludePad, effectiveEllipsize,
ellipsisWidth);
}
- } else if (shouldEllipsize) {
- result = new StaticLayout(mTransformed,
- 0, mTransformed.length(),
- mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
- mSpacingAdd, mIncludePad, effectiveEllipsize,
- ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
- } else {
- result = new StaticLayout(mTransformed, mTextPaint,
- wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
- mIncludePad);
}
- } else if (shouldEllipsize) {
- result = new StaticLayout(mTransformed,
- 0, mTransformed.length(),
- mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
- mSpacingAdd, mIncludePad, effectiveEllipsize,
- ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
- } else {
- result = new StaticLayout(mTransformed, mTextPaint,
- wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
- mIncludePad);
}
}
+ if (result == null) {
+ StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
+ 0, mTransformed.length(), wantWidth)
+ .setPaint(mTextPaint)
+ .setAlignment(alignment)
+ .setTextDir(mTextDir)
+ .setSpacingMult(mSpacingMult)
+ .setSpacingAdd(mSpacingAdd)
+ .setIncludePad(mIncludePad)
+ .setBreakStrategy(mBreakStrategy);
+ if (shouldEllipsize) {
+ builder.setEllipsize(effectiveEllipsize)
+ .setEllipsizedWidth(ellipsisWidth)
+ .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
+ }
+ // TODO: explore always setting maxLines
+ result = builder.build();
+ }
return result;
}
@@ -8576,8 +8606,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
- public void onProvideAssistStructure(ViewAssistStructure structure, Bundle extras) {
- super.onProvideAssistStructure(structure, extras);
+ public void onProvideAssistStructure(ViewAssistStructure structure) {
+ super.onProvideAssistStructure(structure);
final boolean isPassword = hasPasswordTransformationMethod();
if (!isPassword) {
structure.setText(getText(), getSelectionStart(), getSelectionEnd());
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 944b491..986c0f8 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -16,6 +16,7 @@
package android.widget;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Widget;
import android.content.Context;
@@ -29,18 +30,13 @@ import com.android.internal.R;
import java.util.Locale;
/**
- * A view for selecting the time of day, in either 24 hour or AM/PM mode. The
- * hour, each minute digit, and AM/PM (if applicable) can be conrolled by
- * vertical spinners. The hour can be entered by keyboard input. Entering in two
- * digit hours can be accomplished by hitting two digits within a timeout of
- * about a second (e.g. '1' then '2' to select 12). The minutes can be entered
- * by entering single digits. Under AM/PM mode, the user can hit 'a', 'A", 'p'
- * or 'P' to pick. For a dialog using this view, see
- * {@link android.app.TimePickerDialog}.
+ * A widget for selecting the time of day, in either 24-hour or AM/PM mode.
* <p>
- * See the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
- * guide.
- * </p>
+ * For a dialog using this view, see {@link android.app.TimePickerDialog}. See
+ * the <a href="{@docRoot}guide/topics/ui/controls/pickers.html">Pickers</a>
+ * guide for more information.
+ *
+ * @attr ref android.R.styleable#TimePicker_timePickerMode
*/
@Widget
public class TimePicker extends FrameLayout {
@@ -96,44 +92,105 @@ public class TimePicker extends FrameLayout {
}
/**
- * Set the current hour.
+ * Sets the currently selected hour using 24-hour time.
+ *
+ * @param hour the hour to set, in the range (0-23)
+ * @see #getHour()
+ */
+ public void setHour(int hour) {
+ mDelegate.setCurrentHour(hour);
+ }
+
+ /**
+ * Returns the currently selected hour using 24-hour time.
+ *
+ * @return the currently selected hour, in the range (0-23)
+ * @see #setHour(int)
+ */
+ public int getHour() {
+ return mDelegate.getCurrentHour();
+ }
+
+ /**
+ * Sets the currently selected minute..
+ *
+ * @param minute the minute to set, in the range (0-59)
+ * @see #getMinute()
*/
- public void setCurrentHour(Integer currentHour) {
- mDelegate.setCurrentHour(currentHour);
+ public void setMinute(int minute) {
+ mDelegate.setCurrentMinute(minute);
}
/**
- * @return The current hour in the range (0-23).
+ * Returns the currently selected minute.
+ *
+ * @return the currently selected minute, in the range (0-59)
+ * @see #setMinute(int)
*/
+ public int getMinute() {
+ return mDelegate.getCurrentMinute();
+ }
+
+ /**
+ * Sets the current hour.
+ *
+ * @deprecated Use {@link #setHour(int)}
+ */
+ @Deprecated
+ public void setCurrentHour(@NonNull Integer currentHour) {
+ setHour(currentHour);
+ }
+
+ /**
+ * @return the current hour in the range (0-23)
+ * @deprecated Use {@link #getHour()}
+ */
+ @NonNull
+ @Deprecated
public Integer getCurrentHour() {
return mDelegate.getCurrentHour();
}
/**
* Set the current minute (0-59).
+ *
+ * @deprecated Use {@link #setMinute(int)}
*/
- public void setCurrentMinute(Integer currentMinute) {
+ @Deprecated
+ public void setCurrentMinute(@NonNull Integer currentMinute) {
mDelegate.setCurrentMinute(currentMinute);
}
/**
- * @return The current minute.
+ * @return the current minute
+ * @deprecated Use {@link #getMinute()}
*/
+ @NonNull
+ @Deprecated
public Integer getCurrentMinute() {
return mDelegate.getCurrentMinute();
}
/**
- * Set whether in 24 hour or AM/PM mode.
+ * Sets whether this widget displays time in 24-hour mode or 12-hour mode
+ * with an AM/PM picker.
*
- * @param is24HourView True = 24 hour mode. False = AM/PM.
+ * @param is24HourView {@code true} to display in 24-hour mode,
+ * {@code false} for 12-hour mode with AM/PM
+ * @see #is24HourView()
*/
- public void setIs24HourView(Boolean is24HourView) {
+ public void setIs24HourView(@NonNull Boolean is24HourView) {
+ if (is24HourView == null) {
+ return;
+ }
+
mDelegate.setIs24HourView(is24HourView);
}
/**
- * @return true if this is in 24 hour view else false.
+ * @return {@code true} if this widget displays time in 24-hour mode,
+ * {@code false} otherwise}
+ * @see #setIs24HourView(Boolean)
*/
public boolean is24HourView() {
return mDelegate.is24HourView();
@@ -210,13 +267,13 @@ public class TimePicker extends FrameLayout {
* for the real behavior.
*/
interface TimePickerDelegate {
- void setCurrentHour(Integer currentHour);
- Integer getCurrentHour();
+ void setCurrentHour(int currentHour);
+ int getCurrentHour();
- void setCurrentMinute(Integer currentMinute);
- Integer getCurrentMinute();
+ void setCurrentMinute(int currentMinute);
+ int getCurrentMinute();
- void setIs24HourView(Boolean is24HourView);
+ void setIs24HourView(boolean is24HourView);
boolean is24HourView();
void setOnTimeChangedListener(OnTimeChangedListener onTimeChangedListener);
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 9fdd718..2365b48 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -16,16 +16,22 @@
package android.widget;
+import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.SpannableStringBuilder;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
+import android.text.style.TtsSpan;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.StateSet;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.KeyCharacterMap;
@@ -48,7 +54,6 @@ import java.util.Locale;
*/
class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate implements
RadialTimePickerView.OnValueSelectedListener {
-
private static final String TAG = "TimePickerClockDelegate";
// Index used by RadialPickerLayout
@@ -61,14 +66,16 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
// Also NOT a real index, just used for keyboard mode.
private static final int ENABLE_PICKER_INDEX = 3;
+ private static final int[] ATTRS_TEXT_COLOR = new int[] {
+ com.android.internal.R.attr.textColor};
+ private static final int[] ATTRS_DISABLED_ALPHA = new int[] {
+ com.android.internal.R.attr.disabledAlpha};
+
// LayoutLib relies on these constants. Change TimePickerClockDelegate_Delegate if
// modifying these.
static final int AM = 0;
static final int PM = 1;
- private static final boolean DEFAULT_ENABLED_STATE = true;
- private boolean mIsEnabled = DEFAULT_ENABLED_STATE;
-
private static final int HOURS_IN_HALF_DAY = 12;
private final View mHeaderView;
@@ -83,8 +90,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
private final String mAmText;
private final String mPmText;
- private final float mDisabledAlpha;
-
+ private boolean mIsEnabled = true;
private boolean mAllowAutoAdvance;
private int mInitialHourOfDay;
private int mInitialMinute;
@@ -134,7 +140,6 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
final View mainView = inflater.inflate(layoutResourceId, delegator);
mHeaderView = mainView.findViewById(R.id.time_header);
- mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
// Set up hour/minute labels.
mHourView = (TextView) mainView.findViewById(R.id.hours);
@@ -147,41 +152,57 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
mMinuteView.setAccessibilityDelegate(
new ClickActionDelegate(context, R.string.select_minutes));
- final int headerTimeTextAppearance = a.getResourceId(
- R.styleable.TimePicker_headerTimeTextAppearance, 0);
- if (headerTimeTextAppearance != 0) {
- mHourView.setTextAppearance(context, headerTimeTextAppearance);
- mSeparatorView.setTextAppearance(context, headerTimeTextAppearance);
- mMinuteView.setTextAppearance(context, headerTimeTextAppearance);
- }
-
// Now that we have text appearances out of the way, make sure the hour
// and minute views are correctly sized.
mHourView.setMinWidth(computeStableWidth(mHourView, 24));
mMinuteView.setMinWidth(computeStableWidth(mMinuteView, 60));
+ final SpannableStringBuilder amLabel = new SpannableStringBuilder()
+ .append(amPmStrings[0], new TtsSpan.VerbatimBuilder(amPmStrings[0]).build(), 0);
+
// Set up AM/PM labels.
mAmPmLayout = mainView.findViewById(R.id.ampm_layout);
mAmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.am_label);
- mAmLabel.setText(amPmStrings[0]);
+ mAmLabel.setText(obtainVerbatim(amPmStrings[0]));
mAmLabel.setOnClickListener(mClickListener);
mPmLabel = (CheckedTextView) mAmPmLayout.findViewById(R.id.pm_label);
- mPmLabel.setText(amPmStrings[1]);
+ mPmLabel.setText(obtainVerbatim(amPmStrings[1]));
mPmLabel.setOnClickListener(mClickListener);
- final int headerAmPmTextAppearance = a.getResourceId(
- R.styleable.TimePicker_headerAmPmTextAppearance, 0);
- if (headerAmPmTextAppearance != 0) {
- mAmLabel.setTextAppearance(context, headerAmPmTextAppearance);
- mPmLabel.setTextAppearance(context, headerAmPmTextAppearance);
+ // For the sake of backwards compatibility, attempt to extract the text
+ // color from the header time text appearance. If it's set, we'll let
+ // that override the "real" header text color.
+ ColorStateList headerTextColor = null;
+
+ @SuppressWarnings("deprecation")
+ final int timeHeaderTextAppearance = a.getResourceId(
+ R.styleable.TimePicker_headerTimeTextAppearance, 0);
+ if (timeHeaderTextAppearance != 0) {
+ final TypedArray textAppearance = mContext.obtainStyledAttributes(null,
+ ATTRS_TEXT_COLOR, 0, timeHeaderTextAppearance);
+ final ColorStateList legacyHeaderTextColor = textAppearance.getColorStateList(0);
+ headerTextColor = applyLegacyColorFixes(legacyHeaderTextColor);
+ textAppearance.recycle();
}
- a.recycle();
+ if (headerTextColor == null) {
+ headerTextColor = a.getColorStateList(R.styleable.TimePicker_headerTextColor);
+ }
- // Pull disabled alpha from theme.
- final TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
- mDisabledAlpha = outValue.getFloat();
+ if (headerTextColor != null) {
+ mHourView.setTextColor(headerTextColor);
+ mSeparatorView.setTextColor(headerTextColor);
+ mMinuteView.setTextColor(headerTextColor);
+ mAmLabel.setTextColor(headerTextColor);
+ mPmLabel.setTextColor(headerTextColor);
+ }
+
+ // Set up header background, if available.
+ if (a.hasValueOrEmpty(R.styleable.TimePicker_headerBackground)) {
+ mHeaderView.setBackground(a.getDrawable(R.styleable.TimePicker_headerBackground));
+ }
+
+ a.recycle();
mRadialTimePickerView = (RadialTimePickerView) mainView.findViewById(
R.id.radial_picker);
@@ -204,6 +225,59 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX);
}
+ private static final CharSequence obtainVerbatim(String text) {
+ return new SpannableStringBuilder().append(text,
+ new TtsSpan.VerbatimBuilder(text).build(), 0);
+ }
+
+ /**
+ * The legacy text color might have been poorly defined. Ensures that it
+ * has an appropriate activated state, using the selected state if one
+ * exists or modifying the default text color otherwise.
+ *
+ * @param color a legacy text color, or {@code null}
+ * @return a color state list with an appropriate activated state, or
+ * {@code null} if a valid activated state could not be generated
+ */
+ @Nullable
+ private ColorStateList applyLegacyColorFixes(@Nullable ColorStateList color) {
+ if (color == null || color.hasState(R.attr.state_activated)) {
+ return color;
+ }
+
+ final int activatedColor;
+ final int defaultColor;
+ if (color.hasState(R.attr.state_selected)) {
+ activatedColor = color.getColorForState(StateSet.get(
+ StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_SELECTED), 0);
+ defaultColor = color.getColorForState(StateSet.get(
+ StateSet.VIEW_STATE_ENABLED), 0);
+ } else {
+ activatedColor = color.getDefaultColor();
+
+ // Generate a non-activated color using the disabled alpha.
+ final TypedArray ta = mContext.obtainStyledAttributes(ATTRS_DISABLED_ALPHA);
+ final float disabledAlpha = ta.getFloat(0, 0.30f);
+ defaultColor = multiplyAlphaComponent(activatedColor, disabledAlpha);
+ }
+
+ if (activatedColor == 0 || defaultColor == 0) {
+ // We somehow failed to obtain the colors.
+ return null;
+ }
+
+ final int[][] stateSet = new int[][] {{ R.attr.state_activated }, {}};
+ final int[] colors = new int[] { activatedColor, defaultColor };
+ return new ColorStateList(stateSet, colors);
+ }
+
+ private int multiplyAlphaComponent(int color, float alphaMod) {
+ final int srcRgb = color & 0xFFFFFF;
+ final int srcAlpha = (color >> 24) & 0xFF;
+ final int dstAlpha = (int) (srcAlpha * alphaMod + 0.5f);
+ return srcRgb | (dstAlpha << 24);
+ }
+
private static class ClickActionDelegate extends AccessibilityDelegate {
private final AccessibilityAction mClickAction;
@@ -312,7 +386,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
* Set the current hour.
*/
@Override
- public void setCurrentHour(Integer currentHour) {
+ public void setCurrentHour(int currentHour) {
if (mInitialHourOfDay == currentHour) {
return;
}
@@ -329,7 +403,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
* @return The current hour in the range (0-23).
*/
@Override
- public Integer getCurrentHour() {
+ public int getCurrentHour() {
int currentHour = mRadialTimePickerView.getCurrentHour();
if (mIs24HourView) {
return currentHour;
@@ -348,7 +422,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
* Set the current minute (0-59).
*/
@Override
- public void setCurrentMinute(Integer currentMinute) {
+ public void setCurrentMinute(int currentMinute) {
if (mInitialMinute == currentMinute) {
return;
}
@@ -363,7 +437,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
* @return The current minute.
*/
@Override
- public Integer getCurrentMinute() {
+ public int getCurrentMinute() {
return mRadialTimePickerView.getCurrentMinute();
}
@@ -373,7 +447,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
* @param is24HourView True = 24 hour mode. False = AM/PM.
*/
@Override
- public void setIs24HourView(Boolean is24HourView) {
+ public void setIs24HourView(boolean is24HourView) {
if (is24HourView == mIs24HourView) {
return;
}
@@ -604,12 +678,12 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
private void updateAmPmLabelStates(int amOrPm) {
final boolean isAm = amOrPm == AM;
+ mAmLabel.setActivated(isAm);
mAmLabel.setChecked(isAm);
- mAmLabel.setSelected(isAm);
final boolean isPm = amOrPm == PM;
+ mPmLabel.setActivated(isPm);
mPmLabel.setChecked(isPm);
- mPmLabel.setSelected(isPm);
}
/**
@@ -769,8 +843,8 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
}
}
- mHourView.setSelected(index == HOUR_INDEX);
- mMinuteView.setSelected(index == MINUTE_INDEX);
+ mHourView.setActivated(index == HOUR_INDEX);
+ mMinuteView.setActivated(index == MINUTE_INDEX);
}
private void setAmOrPm(int amOrPm) {
@@ -960,9 +1034,9 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
String minuteStr = (values[1] == -1) ? mDoublePlaceholderText :
String.format(minuteFormat, values[1]).replace(' ', mPlaceholderText);
mHourView.setText(hourStr);
- mHourView.setSelected(false);
+ mHourView.setActivated(false);
mMinuteView.setText(minuteStr);
- mMinuteView.setSelected(false);
+ mMinuteView.setActivated(false);
if (!mIs24HourView) {
updateAmPmLabelStates(values[2]);
}
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index 513c55b..df6b0a9 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -279,13 +279,13 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
@Override
- public void setCurrentHour(Integer currentHour) {
+ public void setCurrentHour(int currentHour) {
setCurrentHour(currentHour, true);
}
- private void setCurrentHour(Integer currentHour, boolean notifyTimeChanged) {
+ private void setCurrentHour(int currentHour, boolean notifyTimeChanged) {
// why was Integer used in the first place?
- if (currentHour == null || currentHour == getCurrentHour()) {
+ if (currentHour == getCurrentHour()) {
return;
}
if (!is24HourView()) {
@@ -310,7 +310,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
@Override
- public Integer getCurrentHour() {
+ public int getCurrentHour() {
int currentHour = mHourSpinner.getValue();
if (is24HourView()) {
return currentHour;
@@ -322,7 +322,7 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
@Override
- public void setCurrentMinute(Integer currentMinute) {
+ public void setCurrentMinute(int currentMinute) {
if (currentMinute == getCurrentMinute()) {
return;
}
@@ -331,12 +331,12 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
}
@Override
- public Integer getCurrentMinute() {
+ public int getCurrentMinute() {
return mMinuteSpinner.getValue();
}
@Override
- public void setIs24HourView(Boolean is24HourView) {
+ public void setIs24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java
index 7bd502e..7182414 100644
--- a/core/java/android/widget/YearPickerView.java
+++ b/core/java/android/widget/YearPickerView.java
@@ -111,16 +111,12 @@ class YearPickerView extends ListView {
mAdapter.setRange(min, max);
}
- public void setYearTextAppearance(int resId) {
- mAdapter.setItemTextAppearance(resId);
- }
-
- public void setYearActivatedTextAppearance(int resId) {
- mAdapter.setItemActivatedTextAppearance(resId);
- }
-
private static class YearAdapter extends BaseAdapter {
private static final int ITEM_LAYOUT = R.layout.year_label_text_view;
+ private static final int ITEM_TEXT_APPEARANCE =
+ R.style.TextAppearance_Material_DatePicker_List_YearLabel;
+ private static final int ITEM_TEXT_ACTIVATED_APPEARANCE =
+ R.style.TextAppearance_Material_DatePicker_List_YearLabel_Activated;
private final LayoutInflater mInflater;
@@ -128,9 +124,6 @@ class YearPickerView extends ListView {
private int mMinYear;
private int mCount;
- private int mItemTextAppearanceResId;
- private int mItemActivatedTextAppearanceResId;
-
public YearAdapter(Context context) {
mInflater = LayoutInflater.from(context);
}
@@ -155,16 +148,6 @@ class YearPickerView extends ListView {
return false;
}
- public void setItemTextAppearance(int resId) {
- mItemTextAppearanceResId = resId;
- notifyDataSetChanged();
- }
-
- public void setItemActivatedTextAppearance(int resId) {
- mItemActivatedTextAppearanceResId = resId;
- notifyDataSetChanged();
- }
-
@Override
public int getCount() {
return mCount;
@@ -203,10 +186,10 @@ class YearPickerView extends ListView {
final boolean activated = mActivatedYear == year;
final int textAppearanceResId;
- if (activated && mItemActivatedTextAppearanceResId != 0) {
- textAppearanceResId = mItemActivatedTextAppearanceResId;
+ if (activated && ITEM_TEXT_ACTIVATED_APPEARANCE != 0) {
+ textAppearanceResId = ITEM_TEXT_ACTIVATED_APPEARANCE;
} else {
- textAppearanceResId = mItemTextAppearanceResId;
+ textAppearanceResId = ITEM_TEXT_APPEARANCE;
}
final TextView v = (TextView) convertView;