summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-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/app/ApplicationPackageManager.java40
-rw-r--r--core/java/android/app/IBackupAgent.aidl5
-rw-r--r--core/java/android/app/SystemServiceRegistry.java8
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java35
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl1
-rw-r--r--core/java/android/app/backup/BackupAgent.java55
-rw-r--r--core/java/android/app/backup/BackupTransport.java25
-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/content/Intent.java13
-rw-r--r--core/java/android/content/IntentFilter.java148
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java7
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl6
-rw-r--r--core/java/android/content/pm/IntentFilterVerificationInfo.aidl19
-rw-r--r--core/java/android/content/pm/IntentFilterVerificationInfo.java224
-rw-r--r--core/java/android/content/pm/PackageManager.java187
-rw-r--r--core/java/android/content/pm/PackageParser.java34
-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/Configuration.java3
-rw-r--r--core/java/android/content/res/Resources.java51
-rw-r--r--core/java/android/database/DatabaseErrorHandler.java5
-rwxr-xr-xcore/java/android/database/DefaultDatabaseErrorHandler.java4
-rw-r--r--core/java/android/hardware/SystemSensorManager.java27
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java3
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java3
-rw-r--r--core/java/android/hardware/hdmi/HdmiTvClient.java21
-rw-r--r--core/java/android/net/NetworkStats.java41
-rw-r--r--core/java/android/net/TrafficStats.java2
-rw-r--r--core/java/android/os/BatteryStats.java6
-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/IMountService.java8
-rw-r--r--core/java/android/os/storage/StorageManager.java93
-rw-r--r--core/java/android/os/storage/StorageVolume.java64
-rw-r--r--core/java/android/preference/Preference.java2
-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/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.java11
-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.java49
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java6
-rw-r--r--core/java/android/text/DynamicLayout.java19
-rw-r--r--core/java/android/text/Hyphenator.java76
-rw-r--r--core/java/android/text/Layout.java37
-rw-r--r--core/java/android/text/StaticLayout.java96
-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/util/DebugUtils.java57
-rw-r--r--core/java/android/view/IGraphicsStats.aidl26
-rw-r--r--core/java/android/view/ThreadedRenderer.java42
-rw-r--r--core/java/android/view/View.java8
-rw-r--r--core/java/android/view/ViewDebug.java38
-rw-r--r--core/java/android/view/ViewRootImpl.java96
-rw-r--r--core/java/android/view/Window.java4
-rw-r--r--core/java/android/webkit/WebChromeClient.java8
-rw-r--r--core/java/android/webkit/WebViewClient.java2
-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.java11
-rw-r--r--core/java/android/widget/Editor.java218
-rw-r--r--core/java/android/widget/RadialTimePickerView.java141
-rw-r--r--core/java/android/widget/SimpleMonthView.java355
-rw-r--r--core/java/android/widget/TimePicker.java109
-rw-r--r--core/java/android/widget/TimePickerClockDelegate.java136
-rw-r--r--core/java/android/widget/TimePickerSpinnerDelegate.java14
-rw-r--r--core/java/android/widget/YearPickerView.java31
78 files changed, 2436 insertions, 1006 deletions
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/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 6d74905..6ec5457 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;
@@ -1309,6 +1310,45 @@ 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 void setInstallerPackageName(String targetPackage,
String installerPackageName) {
try {
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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 59fe490..a0f40f6 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ea48b61..cbb0f51 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";
@@ -3162,6 +3165,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..73b0684 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);
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..ca6dc69 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.
@@ -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/content/Intent.java b/core/java/android/content/Intent.java
index 030b770..7a99a79 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1911,6 +1911,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.
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/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 8f17845..2496e45 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;
@@ -655,7 +654,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);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index c6d97f1..66b0d1a 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;
@@ -436,6 +437,11 @@ 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> outFailedDomains);
+ int getIntentVerificationStatus(String packageName, int userId);
+ boolean updateIntentVerificationStatus(String packageName, int status, int userId);
+ List<IntentFilterVerificationInfo> getIntentFilterVerifications(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..60cb4a8
--- /dev/null
+++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java
@@ -0,0 +1,224 @@
+/*
+ * 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.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 String[] mDomains;
+ private String mPackageName;
+ private int mMainStatus;
+
+ public IntentFilterVerificationInfo() {
+ mPackageName = null;
+ mDomains = new String[0];
+ mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
+ }
+
+ public IntentFilterVerificationInfo(String packageName, 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 String[] getDomains() {
+ return 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;
+
+ ArrayList<String> list = 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)) {
+ if (list == null) {
+ list = new ArrayList<>();
+ }
+ list.add(name);
+ }
+ } else {
+ Log.w(TAG, "Unknown tag parsing IntentFilter: " + tagName);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+
+ mDomains = list.toArray(new String[list.size()]);
+ }
+
+ 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 = source.readStringArray();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mPackageName);
+ dest.writeInt(mMainStatus);
+ dest.writeStringArray(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/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 59a16da..46d6ffb3 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -969,6 +969,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 +1654,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 +1734,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 +3559,85 @@ 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 for the provided userId.
+ * @return a list of IntentFilterVerificationInfo for a specific package and User.
+ */
+ public abstract List<IntentFilterVerificationInfo> getIntentFilterVerifications(
+ String packageName);
+
+ /**
* Change the installer associated with a given package. There are limitations
* on how the installer package can be changed; in particular:
* <ul>
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 212cf6d..4b81fd4 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2750,15 +2750,21 @@ public class PackageParser {
}
}
- addSharedLibrariesForBackwardCompatibility(owner);
+ modifySharedLibrariesForBackwardCompatibility(owner);
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");
}
/**
@@ -3149,7 +3155,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 +3167,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 +3347,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 +3527,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 +3786,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 +3987,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 +4012,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();
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/Configuration.java b/core/java/android/content/res/Configuration.java
index 14af584..b5eeb30 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1314,7 +1314,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..44018ff 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -111,12 +111,12 @@ 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>();
+ = new LongSparseArray<>();
private static final LongSparseArray<ColorStateListFactory> sPreloadedColorStateLists
- = new LongSparseArray<ColorStateListFactory>();
+ = new LongSparseArray<>();
// 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 +128,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 +155,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<>();
}
/**
@@ -1876,7 +1874,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 +1889,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 +1900,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,
@@ -2508,10 +2510,10 @@ public class Resources {
// Clean out the caches before we add more. This shouldn't
// happen very often.
pruneCaches(caches);
- themedCache = new LongSparseArray<WeakReference<ConstantState>>(1);
+ themedCache = new LongSparseArray<>(1);
caches.put(themeKey, themedCache);
}
- themedCache.put(key, new WeakReference<ConstantState>(cs));
+ themedCache.put(key, new WeakReference<>(cs));
}
}
}
@@ -2830,15 +2832,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/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/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..e346dc2 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
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/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/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 1a06e0a..8b3ecae 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -3233,9 +3233,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);
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 975bfc2..2db976e 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[] dirs = new File[volumes.length];
+ for (int i = 0; i < volumes.length; i++) {
+ dirs[i] = volumes[i].getPathFile();
}
-
- mExternalDirsForVold = externalForVold.toArray(new File[externalForVold.size()]);
- mExternalDirsForApp = externalForApp.toArray(new File[externalForApp.size()]);
+ return dirs;
}
@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/IMountService.java b/core/java/android/os/storage/IMountService.java
index 6209c2a..fef12d1 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);
@@ -1308,7 +1309,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;
@@ -1630,7 +1632,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.
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 2785ee8..532bf2c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -18,19 +18,23 @@ 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 libcore.util.EmptyArray;
+
import com.android.internal.util.Preconditions;
import java.io.File;
@@ -60,6 +64,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class StorageManager {
private static final String TAG = "StorageManager";
+ private final Context mContext;
private final ContentResolver mResolver;
/*
@@ -311,8 +316,9 @@ public class StorageManager {
*
* @hide
*/
- public StorageManager(ContentResolver resolver, Looper tgtLooper) throws RemoteException {
- mResolver = resolver;
+ public StorageManager(Context context, Looper tgtLooper) {
+ mContext = context;
+ mResolver = context.getContentResolver();
mTgtLooper = tgtLooper;
mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
if (mMountService == null) {
@@ -548,17 +554,46 @@ public class StorageManager {
return null;
}
+ /** {@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;
+ }
+
/**
* Gets the state of a volume via its mountpoint.
* @hide
*/
- public String getVolumeState(String mountPoint) {
- if (mMountService == null) return Environment.MEDIA_REMOVED;
- try {
- return mMountService.getVolumeState(mountPoint);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get volume state", e);
- return null;
+ @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;
}
}
@@ -566,20 +601,22 @@ public class StorageManager {
* Returns list of all mountable volumes.
* @hide
*/
- public StorageVolume[] getVolumeList() {
- if (mMountService == null) return new StorageVolume[0];
+ public @NonNull StorageVolume[] getVolumeList() {
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 mMountService.getVolumeList(mContext.getUserId());
} catch (RemoteException e) {
- Log.e(TAG, "Failed to get volume list", e);
- return null;
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /** {@hide} */
+ public static @NonNull StorageVolume[] getVolumeList(int userId) {
+ final IMountService mountService = IMountService.Stub.asInterface(
+ ServiceManager.getService("mount"));
+ try {
+ return mountService.getVolumeList(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -587,9 +624,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 +636,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..0c391ca 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -17,6 +17,7 @@
package android.os.storage;
import android.content.Context;
+import android.net.TrafficStats;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
@@ -34,52 +35,58 @@ import java.io.File;
*/
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 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 mUuid;
+ private final String mUserLabel;
+ 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) {
+ public StorageVolume(String id, int storageId, File path, int descriptionId, boolean primary,
+ boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
+ long maxFileSize, UserHandle owner, String uuid, String userLabel, String state) {
+ mId = id;
+ mStorageId = storageId;
mPath = path;
mDescriptionId = descriptionId;
mPrimary = primary;
mRemovable = removable;
mEmulated = emulated;
- mMtpReserveSpace = mtpReserveSpace;
+ mMtpReserveSize = mtpReserveSize;
mAllowMassStorage = allowMassStorage;
mMaxFileSize = maxFileSize;
mOwner = owner;
+ mUuid = uuid;
+ mUserLabel = userLabel;
+ mState = state;
}
private StorageVolume(Parcel in) {
+ mId = in.readString();
mStorageId = in.readInt();
mPath = new File(in.readString());
mDescriptionId = in.readInt();
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);
@@ -88,10 +95,8 @@ public class StorageVolume implements Parcelable {
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;
}
/**
@@ -153,15 +158,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 +170,7 @@ public class StorageVolume implements Parcelable {
* @return MTP reserve space
*/
public int getMtpReserveSpace() {
- return mMtpReserveSpace;
+ return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
}
/**
@@ -199,10 +195,6 @@ public class StorageVolume implements Parcelable {
return mOwner;
}
- public void setUuid(String uuid) {
- mUuid = uuid;
- }
-
public String getUuid() {
return mUuid;
}
@@ -222,18 +214,10 @@ public class StorageVolume implements Parcelable {
}
}
- public void setUserLabel(String userLabel) {
- mUserLabel = userLabel;
- }
-
public String getUserLabel() {
return mUserLabel;
}
- public void setState(String state) {
- mState = state;
- }
-
public String getState() {
return mState;
}
@@ -262,13 +246,14 @@ 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("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);
@@ -297,13 +282,14 @@ 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.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);
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/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/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..8e5d245 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5255,6 +5255,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 ':'.
*
@@ -6088,7 +6097,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
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..c2ebbc6 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
@@ -224,7 +227,53 @@ 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_TAG_LENGTH,
+ "Unsupported 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_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/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 7a5bb90..3245f55 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -512,11 +512,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);
}
}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 1bdaef0..7d2e1ef 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -356,6 +356,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 +633,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;
@@ -739,11 +749,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..f4dff9b
--- /dev/null
+++ b/core/java/android/text/Hyphenator.java
@@ -0,0 +1,76 @@
+/*
+ * 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
+ */
+/* package */ 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) {
+ synchronized (sMap) {
+ Hyphenator result = sMap.get(locale);
+ if (result == null) {
+ result = loadHyphenator(locale);
+ sMap.put(locale, result);
+ }
+ 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("/data/local/tmp/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);
+ }
+ }
+ return null;
+ }
+}
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index fcf1828..22abb18 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -225,17 +225,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 +254,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 +280,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 +304,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 +333,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 +346,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 +678,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 +1163,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/StaticLayout.java b/core/java/android/text/StaticLayout.java
index ee39e27..4174df0 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -170,8 +170,10 @@ public class StaticLayout extends Layout {
* 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 +188,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;
}
}
@@ -459,7 +461,26 @@ 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;
+ }
+ }
+
+ int breakStrategy = 0; // 0 = kBreakStrategy_Greedy
+ nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
+ firstWidth, firstWidthLineCount, restWidth,
+ variableTabStops, TAB_INCREMENT, breakStrategy);
// measurement has to be done before performing line breaking
// but we don't want to recompute fontmetrics or span ranges the
@@ -505,29 +526,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 +618,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 +630,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 +723,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 +945,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 +979,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 +1004,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 +1043,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/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/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/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..a69384a 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3196,9 +3196,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();
}
@@ -9959,6 +9959,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 +11164,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
invalidateViewProperty(false, true);
invalidateParentIfNeededAndWasQuickRejected();
+ notifySubtreeAccessibilityStateChangedIfNeeded();
}
}
@@ -16674,6 +16677,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
mForegroundInfo.mDrawable = foreground;
+ mForegroundInfo.mBoundsChanged = true;
if (foreground != null) {
setWillNotDraw(false);
foreground.setCallback(this);
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/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/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/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/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..e2f8efc 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);
+ }
});
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 87fcd81..82e36c3 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,24 @@ 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());
+ getInsertionController().getHandle();
+ mInsertionHandleHeight = mSelectHandleCenter.getMinimumHeight();
+ }
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
@@ -2956,13 +2973,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 +3000,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 +3074,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 +3117,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 +4062,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 +4623,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 +4643,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 +4661,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 +4741,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 +4751,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 +4781,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 +4792,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 +4843,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 +4900,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/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 20aa972..143dea4 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -122,8 +122,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();
@@ -147,6 +148,8 @@ public class RadialTimePickerView extends View {
private final RadialPickerTouchHelper mTouchHelper;
+ private final Path mSelectorPath = new Path();
+
private boolean mIs24HourMode;
private boolean mShowHours;
@@ -316,11 +319,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 +343,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)));
@@ -600,8 +593,8 @@ public class RadialTimePickerView extends View {
// 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 +605,16 @@ public class RadialTimePickerView extends View {
mInnerTextHours = mInnerHours24Texts;
} else {
mOuterTextHours = mHours12Texts;
- mInnerTextHours = null;
+ mInnerTextHours = mHours12Texts;
}
mOuterTextMinutes = 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
@@ -675,7 +662,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,
@@ -714,69 +701,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]);
+ 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.
+ // Draw the dot if we're between two items.
final boolean shouldDrawDot = mSelectionDegrees[index] % 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(color);
+ 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 +869,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 +885,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()) {
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index 4e5a39a..3fb096c 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,13 +73,27 @@ 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;
+ private final int mMonthHeight;
+ private final int mDayOfWeekHeight;
+ private final int mDayHeight;
+ private final int mCellWidth;
+ private final int mDaySelectorRadius;
+
+ // 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;
@@ -81,12 +102,6 @@ class SimpleMonthView extends View {
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 +137,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);
@@ -146,6 +164,13 @@ class SimpleMonthView extends View {
mCellWidth = res.getDimensionPixelSize(R.dimen.date_picker_day_width);
mDaySelectorRadius = 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);
setAccessibilityDelegate(mTouchHelper);
@@ -160,6 +185,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 +229,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 +346,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 +381,7 @@ class SimpleMonthView extends View {
drawMonth(canvas);
drawDaysOfWeek(canvas);
drawDays(canvas);
+ drawButtons(canvas);
canvas.translate(-paddingLeft, -paddingTop);
}
@@ -347,34 +397,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 = mPaddedWidth / DAYS_IN_WEEK;
+
+ // 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 = mPaddedWidth / DAYS_IN_WEEK;
- // 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 +445,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 +461,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;
}
@@ -569,8 +638,42 @@ class SimpleMonthView extends View {
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- mPaddedWidth = w - getPaddingLeft() - getPaddingRight();
- mPaddedHeight = w - getPaddingTop() - getPaddingBottom();
+ final int paddedLeft = getPaddingLeft();
+ final int paddedTop = getPaddingTop();
+ final int paddedRight = w - getPaddingRight();
+ final int paddedBottom = h - getPaddingBottom();
+ mPaddedWidth = paddedRight - paddedLeft;
+ mPaddedHeight = paddedBottom - paddedTop;
+
+ final int monthHeight = mMonthHeight;
+ final int cellWidth = mPaddedWidth / DAYS_IN_WEEK;
+
+ // 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, paddedLeft + cellWidth, paddedTop + 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 = mPaddedWidth - (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, paddedTop + monthHeight);
+ }
// Invalidate cached accessibility information.
mTouchHelper.invalidateRoot();
@@ -585,22 +688,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 +729,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 = mPaddedWidth / DAYS_IN_WEEK;
+ 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 +838,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 +847,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 +867,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 +878,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 +895,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 +942,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/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..c58d5cb 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -16,16 +16,20 @@
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.format.DateFormat;
import android.text.format.DateUtils;
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 +52,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 +64,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 +88,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 +138,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,14 +150,6 @@ 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));
@@ -169,19 +164,40 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
mPmLabel.setText(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);
+ }
+
+ 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));
+ }
- // Pull disabled alpha from theme.
- final TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, outValue, true);
- mDisabledAlpha = outValue.getFloat();
+ a.recycle();
mRadialTimePickerView = (RadialTimePickerView) mainView.findViewById(
R.id.radial_picker);
@@ -204,6 +220,54 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
initialize(currentHour, currentMinute, false /* 12h */, HOUR_INDEX);
}
+ /**
+ * 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 +376,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 +393,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 +412,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 +427,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
* @return The current minute.
*/
@Override
- public Integer getCurrentMinute() {
+ public int getCurrentMinute() {
return mRadialTimePickerView.getCurrentMinute();
}
@@ -373,7 +437,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 +668,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 +833,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 +1024,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;