summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/java/android/app/ContextImpl.java6
-rw-r--r--core/java/android/content/ContentResolver.java26
-rw-r--r--core/java/android/content/Context.java11
-rw-r--r--core/java/android/content/PeriodicSync.java71
-rw-r--r--core/java/android/content/SyncRequest.java193
-rw-r--r--core/java/android/content/SyncService.java169
-rw-r--r--core/java/android/ddm/DdmHandleProfiling.java2
-rw-r--r--core/java/android/nfc/cardemulation/ApduServiceInfo.java14
-rw-r--r--core/java/android/nfc/cardemulation/CardEmulation.java343
-rw-r--r--core/java/android/nfc/cardemulation/CardEmulationManager.java4
-rw-r--r--core/java/android/nfc/cardemulation/HostApduService.java20
-rw-r--r--core/java/android/nfc/cardemulation/OffHostApduService.java5
-rw-r--r--core/java/android/os/Debug.java8
-rw-r--r--core/java/android/provider/DocumentsContract.java756
-rw-r--r--core/java/android/provider/DocumentsProvider.java229
-rw-r--r--core/java/android/provider/Settings.java46
-rw-r--r--core/java/android/security/IKeystoreService.java16
-rw-r--r--core/java/android/speech/tts/SynthesisRequest.java2
-rw-r--r--core/java/android/util/LayoutDirection.java8
-rw-r--r--core/java/android/view/accessibility/CaptioningManager.java300
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java10
-rw-r--r--core/java/android/webkit/WebView.java26
-rw-r--r--core/java/android/webkit/WebViewClassic.java7
-rw-r--r--core/java/android/webkit/WebViewProvider.java8
-rw-r--r--core/java/com/android/internal/widget/AutoScrollHelper.java432
-rw-r--r--core/jni/com_google_android_gles_jni_EGLImpl.cpp4
-rw-r--r--core/res/AndroidManifest.xml6
-rw-r--r--core/res/res/values/attrs.xml8
-rw-r--r--core/tests/coretests/Android.mk2
-rw-r--r--core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java1809
-rw-r--r--core/tests/coretests/src/android/webkit/AccessibilityInjectorTestActivity.java38
-rw-r--r--core/tests/coretests/src/android/webkit/UrlInterceptRegistryTest.java88
-rw-r--r--core/tests/coretests/src/android/webkit/WebkitTest.java59
-rw-r--r--core/tests/coretests/src/android/webkit/ZoomManagerTest.java128
34 files changed, 1680 insertions, 3174 deletions
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index cdec399..f10290d 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -105,6 +105,7 @@ import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.CaptioningManager;
import android.view.inputmethod.InputMethodManager;
import android.view.textservice.TextServicesManager;
import android.accounts.AccountManager;
@@ -307,6 +308,11 @@ class ContextImpl extends Context {
return AccessibilityManager.getInstance(ctx);
}});
+ registerService(CAPTIONING_SERVICE, new ServiceFetcher() {
+ public Object getService(ContextImpl ctx) {
+ return new CaptioningManager(ctx);
+ }});
+
registerService(ACCOUNT_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index a761a89..8a5a56c 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -141,7 +141,7 @@ public abstract class ContentResolver {
public static final String SYNC_EXTRAS_PRIORITY = "sync_priority";
/** {@hide} Flag to allow sync to occur on metered network. */
- public static final String SYNC_EXTRAS_ALLOW_METERED = "allow_metered";
+ public static final String SYNC_EXTRAS_DISALLOW_METERED = "disallow_metered";
/**
* Set by the SyncManager to request that the SyncAdapter initialize itself for
@@ -1669,7 +1669,7 @@ public abstract class ContentResolver {
new SyncRequest.Builder()
.setSyncAdapter(account, authority)
.setExtras(extras)
- .syncOnce(0, 0) // Immediate sync.
+ .syncOnce()
.build();
requestSync(request);
}
@@ -1677,6 +1677,9 @@ public abstract class ContentResolver {
/**
* Register a sync with the SyncManager. These requests are built using the
* {@link SyncRequest.Builder}.
+ *
+ * @param request The immutable SyncRequest object containing the sync parameters. Use
+ * {@link SyncRequest.Builder} to construct these.
*/
public static void requestSync(SyncRequest request) {
try {
@@ -1812,6 +1815,9 @@ public abstract class ContentResolver {
* {@link #SYNC_EXTRAS_INITIALIZE}, {@link #SYNC_EXTRAS_FORCE},
* {@link #SYNC_EXTRAS_EXPEDITED}, {@link #SYNC_EXTRAS_MANUAL} set to true.
* If any are supplied then an {@link IllegalArgumentException} will be thrown.
+ * <p>As of API level 19 this function introduces a default flexibility of ~4% (up to a maximum
+ * of one hour in the day) into the requested period. Use
+ * {@link SyncRequest.Builder#syncPeriodic(long, long)} to set this flexibility manually.
*
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#WRITE_SYNC_SETTINGS}.
@@ -1875,22 +1881,6 @@ public abstract class ContentResolver {
}
/**
- * Remove the specified sync. This will remove any syncs that have been scheduled to run, but
- * will not cancel any running syncs.
- * <p>This method requires the caller to hold the permission</p>
- * If the request is for a periodic sync this will cancel future occurrences of the sync.
- *
- * It is possible to cancel a sync using a SyncRequest object that is different from the object
- * with which you requested the sync. Do so by building a SyncRequest with exactly the same
- * service/adapter, frequency, <b>and</b> extras bundle.
- *
- * @param request SyncRequest object containing information about sync to cancel.
- */
- public static void cancelSync(SyncRequest request) {
- // TODO: Finish this implementation.
- }
-
- /**
* Get the list of information about the periodic syncs for the given account and authority.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#READ_SYNC_SETTINGS}.
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cd1f87b..2ff9182 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2011,6 +2011,17 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.view.accessibility.CaptioningManager} for obtaining
+ * captioning properties and listening for changes in captioning
+ * preferences.
+ *
+ * @see #getSystemService
+ * @see android.view.accessibility.CaptioningManager
+ */
+ public static final String CAPTIONING_SERVICE = "captioning";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.app.NotificationManager} for controlling keyguard.
*
* @see #getSystemService
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index 6aca151..b586eec 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -29,17 +29,13 @@ public class PeriodicSync implements Parcelable {
public final Account account;
/** The authority of the sync. Can be null. */
public final String authority;
- /** The service for syncing, if this is an anonymous sync. Can be null.*/
- public final ComponentName service;
/** Any extras that parameters that are to be passed to the sync adapter. */
public final Bundle extras;
/** How frequently the sync should be scheduled, in seconds. Kept around for API purposes. */
public final long period;
- /** Whether this periodic sync uses a service. */
- public final boolean isService;
/**
- * How much flexibility can be taken in scheduling the sync, in seconds.
* {@hide}
+ * How much flexibility can be taken in scheduling the sync, in seconds.
*/
public final long flexTime;
@@ -52,76 +48,44 @@ public class PeriodicSync implements Parcelable {
public PeriodicSync(Account account, String authority, Bundle extras, long periodInSeconds) {
this.account = account;
this.authority = authority;
- this.service = null;
- this.isService = false;
if (extras == null) {
this.extras = new Bundle();
} else {
this.extras = new Bundle(extras);
}
this.period = periodInSeconds;
- // Old API uses default flex time. No-one should be using this ctor anyway.
+ // Initialise to a sane value.
this.flexTime = 0L;
}
- // TODO: Add copy ctor from SyncRequest?
-
/**
- * Create a copy of a periodic sync.
* {@hide}
+ * Create a copy of a periodic sync.
*/
public PeriodicSync(PeriodicSync other) {
this.account = other.account;
this.authority = other.authority;
- this.service = other.service;
- this.isService = other.isService;
this.extras = new Bundle(other.extras);
this.period = other.period;
this.flexTime = other.flexTime;
}
/**
- * A PeriodicSync for a sync with a specified provider.
* {@hide}
+ * A PeriodicSync for a sync with a specified provider.
*/
public PeriodicSync(Account account, String authority, Bundle extras,
long period, long flexTime) {
this.account = account;
this.authority = authority;
- this.service = null;
- this.isService = false;
- this.extras = new Bundle(extras);
- this.period = period;
- this.flexTime = flexTime;
- }
-
- /**
- * A PeriodicSync for a sync with a specified SyncService.
- * {@hide}
- */
- public PeriodicSync(ComponentName service, Bundle extras,
- long period,
- long flexTime) {
- this.account = null;
- this.authority = null;
- this.service = service;
- this.isService = true;
this.extras = new Bundle(extras);
this.period = period;
this.flexTime = flexTime;
}
private PeriodicSync(Parcel in) {
- this.isService = (in.readInt() != 0);
- if (this.isService) {
- this.service = in.readParcelable(null);
- this.account = null;
- this.authority = null;
- } else {
- this.account = in.readParcelable(null);
- this.authority = in.readString();
- this.service = null;
- }
+ this.account = in.readParcelable(null);
+ this.authority = in.readString();
this.extras = in.readBundle();
this.period = in.readLong();
this.flexTime = in.readLong();
@@ -134,13 +98,8 @@ public class PeriodicSync implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(isService ? 1 : 0);
- if (account == null && authority == null) {
- dest.writeParcelable(service, flags);
- } else {
- dest.writeParcelable(account, flags);
- dest.writeString(authority);
- }
+ dest.writeParcelable(account, flags);
+ dest.writeString(authority);
dest.writeBundle(extras);
dest.writeLong(period);
dest.writeLong(flexTime);
@@ -167,17 +126,8 @@ public class PeriodicSync implements Parcelable {
return false;
}
final PeriodicSync other = (PeriodicSync) o;
- if (this.isService != other.isService) {
- return false;
- }
- boolean equal = false;
- if (this.isService) {
- equal = service.equals(other.service);
- } else {
- equal = account.equals(other.account)
- && authority.equals(other.authority);
- }
- return equal
+ return account.equals(other.account)
+ && authority.equals(other.authority)
&& period == other.period
&& syncExtrasEquals(extras, other.extras);
}
@@ -208,7 +158,6 @@ public class PeriodicSync implements Parcelable {
public String toString() {
return "account: " + account +
", authority: " + authority +
- ", service: " + service +
". period: " + period + "s " +
", flex: " + flexTime;
}
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
index 4474c70..d4e0c2a 100644
--- a/core/java/android/content/SyncRequest.java
+++ b/core/java/android/content/SyncRequest.java
@@ -20,20 +20,19 @@ import android.accounts.Account;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Pair;
public class SyncRequest implements Parcelable {
private static final String TAG = "SyncRequest";
- /** Account to pass to the sync adapter. Can be null. */
+ /** Account to pass to the sync adapter. May be null. */
private final Account mAccountToSync;
/** Authority string that corresponds to a ContentProvider. */
private final String mAuthority;
- /** {@link SyncService} identifier. */
+ /** Sync service identifier. May be null.*/
private final ComponentName mComponentInfo;
/** Bundle containing user info as well as sync settings. */
private final Bundle mExtras;
- /** Allow this sync request on metered networks. */
- private final boolean mAllowMetered;
+ /** Disallow this sync request on metered networks. */
+ private final boolean mDisallowMetered;
/**
* Anticipated upload size in bytes.
* TODO: Not yet used - we put this information into the bundle for simplicity.
@@ -70,14 +69,18 @@ public class SyncRequest implements Parcelable {
return mIsPeriodic;
}
+ /**
+ * {@hide}
+ * @return whether this is an expedited sync.
+ */
public boolean isExpedited() {
return mIsExpedited;
}
/**
* {@hide}
- * @return true if this sync uses an account/authority pair, or false if
- * this is an anonymous sync bound to an @link AnonymousSyncService.
+ * @return true if this sync uses an account/authority pair, or false if this sync is bound to
+ * a Sync Service.
*/
public boolean hasAuthority() {
return mIsAuthority;
@@ -85,31 +88,30 @@ public class SyncRequest implements Parcelable {
/**
* {@hide}
- * Throws a runtime IllegalArgumentException if this function is called for an
- * anonymous sync.
- *
- * @return (Account, Provider) for this SyncRequest.
+ * @return account object for this sync.
+ * @throws IllegalArgumentException if this function is called for a request that does not
+ * specify an account/provider authority.
*/
- public Pair<Account, String> getProviderInfo() {
+ public Account getAccount() {
if (!hasAuthority()) {
- throw new IllegalArgumentException("Cannot getProviderInfo() for an anonymous sync.");
+ throw new IllegalArgumentException("Cannot getAccount() for a sync that does not"
+ + "specify an authority.");
}
- return Pair.create(mAccountToSync, mAuthority);
+ return mAccountToSync;
}
/**
* {@hide}
- * Throws a runtime IllegalArgumentException if this function is called for a
- * SyncRequest that is bound to an account/provider.
- *
- * @return ComponentName for the service that this sync will bind to.
+ * @return provider for this sync.
+ * @throws IllegalArgumentException if this function is called for a request that does not
+ * specify an account/provider authority.
*/
- public ComponentName getService() {
- if (hasAuthority()) {
- throw new IllegalArgumentException(
- "Cannot getAnonymousService() for a sync that has specified a provider.");
+ public String getProvider() {
+ if (!hasAuthority()) {
+ throw new IllegalArgumentException("Cannot getProvider() for a sync that does not"
+ + "specify a provider.");
}
- return mComponentInfo;
+ return mAuthority;
}
/**
@@ -127,6 +129,7 @@ public class SyncRequest implements Parcelable {
public long getSyncFlexTime() {
return mSyncFlexTimeSecs;
}
+
/**
* {@hide}
* @return the last point in time at which this sync must scheduled.
@@ -159,7 +162,7 @@ public class SyncRequest implements Parcelable {
parcel.writeLong(mSyncFlexTimeSecs);
parcel.writeLong(mSyncRunTimeSecs);
parcel.writeInt((mIsPeriodic ? 1 : 0));
- parcel.writeInt((mAllowMetered ? 1 : 0));
+ parcel.writeInt((mDisallowMetered ? 1 : 0));
parcel.writeLong(mTxBytes);
parcel.writeLong(mRxBytes);
parcel.writeInt((mIsAuthority ? 1 : 0));
@@ -177,7 +180,7 @@ public class SyncRequest implements Parcelable {
mSyncFlexTimeSecs = in.readLong();
mSyncRunTimeSecs = in.readLong();
mIsPeriodic = (in.readInt() != 0);
- mAllowMetered = (in.readInt() != 0);
+ mDisallowMetered = (in.readInt() != 0);
mTxBytes = in.readLong();
mRxBytes = in.readLong();
mIsAuthority = (in.readInt() != 0);
@@ -207,13 +210,13 @@ public class SyncRequest implements Parcelable {
// For now we merge the sync config extras & the custom extras into one bundle.
// TODO: pass the configuration extras through separately.
mExtras.putAll(b.mSyncConfigExtras);
- mAllowMetered = b.mAllowMetered;
+ mDisallowMetered = b.mDisallowMetered;
mTxBytes = b.mTxBytes;
mRxBytes = b.mRxBytes;
}
/**
- * Builder class for a @link SyncRequest. As you build your SyncRequest this class will also
+ * Builder class for a {@link SyncRequest}. As you build your SyncRequest this class will also
* perform validation.
*/
public static class Builder {
@@ -229,12 +232,9 @@ public class SyncRequest implements Parcelable {
private static final int SYNC_TARGET_SERVICE = 1;
/** Specify that this is a sync with a provider. */
private static final int SYNC_TARGET_ADAPTER = 2;
- /**
- * Earliest point of displacement into the future at which this sync can
- * occur.
- */
+ /** Earliest point of displacement into the future at which this sync can occur. */
private long mSyncFlexTimeSecs;
- /** Displacement into the future at which this sync must occur. */
+ /** Latest point of displacement into the future at which this sync must occur. */
private long mSyncRunTimeSecs;
/**
* Sync configuration information - custom user data explicitly provided by the developer.
@@ -253,7 +253,7 @@ public class SyncRequest implements Parcelable {
/** Expected download transfer in bytes. */
private long mRxBytes = -1L;
/** Whether or not this sync can occur on metered networks. Default false. */
- private boolean mAllowMetered;
+ private boolean mDisallowMetered;
/** Priority of this sync relative to others from calling app [-2, 2]. Default 0. */
private int mPriority = 0;
/**
@@ -283,9 +283,8 @@ public class SyncRequest implements Parcelable {
private boolean mExpedited;
/**
- * The {@link SyncService} component that
- * contains the sync logic if this is a provider-less sync, otherwise
- * null.
+ * The sync component that contains the sync logic if this is a provider-less sync,
+ * otherwise null.
*/
private ComponentName mComponentName;
/**
@@ -303,46 +302,28 @@ public class SyncRequest implements Parcelable {
}
/**
- * Developer can define timing constraints for this one-shot request.
- * These values are elapsed real-time.
- *
- * @param whenSeconds The time in seconds at which you want this
- * sync to occur.
- * @param beforeSeconds The amount of time in advance of whenSeconds that this
- * sync may be permitted to occur. This is rounded up to a minimum of 5
- * seconds, for any sync for which whenSeconds > 5.
+ * Request that a sync occur immediately.
*
* Example
* <pre>
- * Perform an immediate sync.
- * SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce(0, 0);
- * That is, a sync 0 seconds from now with 0 seconds of flex.
- *
- * Perform a sync in exactly 5 minutes.
- * SyncRequest.Builder builder =
- * new SyncRequest.Builder().syncOnce(5 * MIN_IN_SECS, 0);
- *
- * Perform a sync in 5 minutes, with one minute of leeway (between 4 and 5 minutes from
- * now).
- * SyncRequest.Builder builder =
- * new SyncRequest.Builder().syncOnce(5 * MIN_IN_SECS, 1 * MIN_IN_SECS);
+ * SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce();
* </pre>
*/
- public Builder syncOnce(long whenSeconds, long beforeSeconds) {
+ public Builder syncOnce() {
if (mSyncType != SYNC_TYPE_UNKNOWN) {
throw new IllegalArgumentException("Sync type has already been defined.");
}
mSyncType = SYNC_TYPE_ONCE;
- setupInterval(whenSeconds, beforeSeconds);
+ setupInterval(0, 0);
return this;
}
/**
* Build a periodic sync. Either this or syncOnce() <b>must</b> be called for this builder.
- * Syncs are identified by target {@link SyncService}/{@link android.provider} and by the
- * contents of the extras bundle.
- * You cannot reuse the same builder for one-time syncs after having specified a periodic
- * sync (by calling this function). If you do, an <code>IllegalArgumentException</code>
+ * Syncs are identified by target {@link android.provider}/{@link android.accounts.Account}
+ * and by the contents of the extras bundle.
+ * You cannot reuse the same builder for one-time syncs (by calling this function) after
+ * having specified a periodic sync. If you do, an <code>IllegalArgumentException</code>
* will be thrown.
*
* Example usage.
@@ -394,6 +375,7 @@ public class SyncRequest implements Parcelable {
}
/**
+ * {@hide}
* Developer can provide insight into their payload size; optional. -1 specifies unknown,
* so that you are not restricted to defining both fields.
*
@@ -407,21 +389,20 @@ public class SyncRequest implements Parcelable {
}
/**
- * @param allow false to allow this transfer on metered networks. Default true.
+ * @see android.net.ConnectivityManager#isActiveNetworkMetered()
+ * @param disallow true to enforce that this transfer not occur on metered networks.
+ * Default false.
*/
- public Builder setAllowMetered(boolean allow) {
- mAllowMetered = true;
+ public Builder setDisallowMetered(boolean disallow) {
+ mDisallowMetered = disallow;
return this;
}
/**
- * Specify an authority and account for this transfer. Cannot be used with
- * {@link #setSyncAdapter(ComponentName cname)}.
+ * Specify an authority and account for this transfer.
*
- * @param authority
- * @param account Account to sync. Can be null unless this is a periodic
- * sync, for which verification by the ContentResolver will
- * fail. If a sync is performed without an account, the
+ * @param authority String identifying which content provider to sync.
+ * @param account Account to sync. Can be null unless this is a periodic sync.
*/
public Builder setSyncAdapter(Account account, String authority) {
if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
@@ -435,26 +416,10 @@ public class SyncRequest implements Parcelable {
}
/**
- * Specify the {@link SyncService} component for this sync. This is not validated until
- * sync time so providing an incorrect component name here will not fail. Cannot be used
- * with {@link #setSyncAdapter(Account account, String authority)}.
- *
- * @param cname ComponentName to identify your Anonymous service
- */
- public Builder setSyncAdapter(ComponentName cname) {
- if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
- throw new IllegalArgumentException("Sync target has already been defined.");
- }
- mSyncTarget = SYNC_TARGET_SERVICE;
- mComponentName = cname;
- mAccount = null;
- mAuthority = null;
- return this;
- }
-
- /**
- * Developer-provided extras handed back when sync actually occurs. This bundle is copied
- * into the SyncRequest returned by {@link #build()}.
+ * Optional developer-provided extras handed back in
+ * {@link AbstractThreadedSyncAdapter#onPerformSync(Account, Bundle, String,
+ * ContentProviderClient, SyncResult)} occurs. This bundle is copied into the SyncRequest
+ * returned by {@link #build()}.
*
* Example:
* <pre>
@@ -468,7 +433,7 @@ public class SyncRequest implements Parcelable {
* Bundle extras = new Bundle();
* extras.setString("data", syncData);
* builder.setExtras(extras);
- * ContentResolver.sync(builder.build()); // Each sync() request creates a unique sync.
+ * ContentResolver.sync(builder.build()); // Each sync() request is for a unique sync.
* }
* </pre>
* Only values of the following types may be used in the extras bundle:
@@ -509,7 +474,8 @@ public class SyncRequest implements Parcelable {
/**
* Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}.
*
- * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
+ * A sync can specify that system sync settings be ignored (user has turned sync off). Not
+ * valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
* {@link #build()}.
*
* @param ignoreSettings true to ignore the sync automatically settings. Default false.
@@ -522,13 +488,13 @@ public class SyncRequest implements Parcelable {
/**
* Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}.
*
- * Ignoring back-off will force the sync scheduling process to ignore any back-off that was
- * the result of a failed sync, as well as to invalidate any {@link SyncResult#delayUntil}
- * value that may have been set by the adapter. Successive failures will not honor this
- * flag. Not valid for periodic sync and will throw an <code>IllegalArgumentException</code>
- * in {@link #build()}.
+ * Force the sync scheduling process to ignore any back-off that was the result of a failed
+ * sync, as well as to invalidate any {@link SyncResult#delayUntil} value that may have
+ * been set by the adapter. Successive failures will not honor this flag. Not valid for
+ * periodic sync and will throw an <code>IllegalArgumentException</code> in
+ * {@link #build()}.
*
- * @param ignoreBackoff ignore back off settings. Default false.
+ * @param ignoreBackoff ignore back-off settings. Default false.
*/
public Builder setIgnoreBackoff(boolean ignoreBackoff) {
mIgnoreBackoff = ignoreBackoff;
@@ -538,8 +504,9 @@ public class SyncRequest implements Parcelable {
/**
* Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_MANUAL}.
*
- * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
- * {@link #build()}.
+ * A manual sync is functionally equivalent to calling {@link #setIgnoreBackoff(boolean)}
+ * and {@link #setIgnoreSettings(boolean)}. Not valid for periodic sync and will throw an
+ * <code>IllegalArgumentException</code> in {@link #build()}.
*
* @param isManual User-initiated sync or not. Default false.
*/
@@ -549,7 +516,7 @@ public class SyncRequest implements Parcelable {
}
/**
- * An expedited sync runs immediately and can preempt other non-expedited running syncs.
+ * An expedited sync runs immediately and will preempt another non-expedited running sync.
*
* Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
* {@link #build()}.
@@ -562,6 +529,7 @@ public class SyncRequest implements Parcelable {
}
/**
+ * {@hide}
* @param priority the priority of this request among all requests from the calling app.
* Range of [-2,2] similar to how this is done with notifications.
*/
@@ -581,18 +549,18 @@ public class SyncRequest implements Parcelable {
* builder.
*/
public SyncRequest build() {
- // Validate the extras bundle
- ContentResolver.validateSyncExtrasBundle(mCustomExtras);
if (mCustomExtras == null) {
mCustomExtras = new Bundle();
}
+ // Validate the extras bundle
+ ContentResolver.validateSyncExtrasBundle(mCustomExtras);
// Combine builder extra flags into the config bundle.
mSyncConfigExtras = new Bundle();
if (mIgnoreBackoff) {
mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
}
- if (mAllowMetered) {
- mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_ALLOW_METERED, true);
+ if (mDisallowMetered) {
+ mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, true);
}
if (mIgnoreSettings) {
mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
@@ -613,13 +581,22 @@ public class SyncRequest implements Parcelable {
// If this is a periodic sync ensure than invalid extras were not set.
validatePeriodicExtras(mCustomExtras);
validatePeriodicExtras(mSyncConfigExtras);
+ // Verify that account and provider are not null.
+ if (mAccount == null) {
+ throw new IllegalArgumentException("Account must not be null for periodic"
+ + " sync.");
+ }
+ if (mAuthority == null) {
+ throw new IllegalArgumentException("Authority must not be null for periodic"
+ + " sync.");
+ }
} else if (mSyncType == SYNC_TYPE_UNKNOWN) {
throw new IllegalArgumentException("Must call either syncOnce() or syncPeriodic()");
}
// Ensure that a target for the sync has been set.
if (mSyncTarget == SYNC_TARGET_UNKNOWN) {
- throw new IllegalArgumentException("Must specify an adapter with one of"
- + "setSyncAdapter(ComponentName) or setSyncAdapter(Account, String");
+ throw new IllegalArgumentException("Must specify an adapter with "
+ + "setSyncAdapter(Account, String");
}
return new SyncRequest(this);
}
diff --git a/core/java/android/content/SyncService.java b/core/java/android/content/SyncService.java
deleted file mode 100644
index 100fd40..0000000
--- a/core/java/android/content/SyncService.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content;
-
-import android.app.Service;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.Trace;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.HashMap;
-
-/**
- * Simplified @link android.content.AbstractThreadedSyncAdapter. Folds that
- * behaviour into a service to which the system can bind when requesting an
- * anonymous (providerless/accountless) sync.
- * <p>
- * In order to perform an anonymous sync operation you must extend this service,
- * implementing the abstract methods. This service must then be declared in the
- * application's manifest as usual. You can use this service for other work, however you
- * <b> must not </b> override the onBind() method unless you know what you're doing,
- * which limits the usefulness of this service for other work.
- *
- * <pre>
- * &lt;service ndroid:name=".MyAnonymousSyncService" android:permission="android.permission.SYNC" /&gt;
- * </pre>
- * Like @link android.content.AbstractThreadedSyncAdapter this service supports
- * multiple syncs at the same time. Each incoming startSync() with a unique tag
- * will spawn a thread to do the work of that sync. If startSync() is called
- * with a tag that already exists, a SyncResult.ALREADY_IN_PROGRESS is returned.
- * Remember that your service will spawn multiple threads if you schedule multiple syncs
- * at once, so if you mutate local objects you must ensure synchronization.
- */
-public abstract class SyncService extends Service {
-
- /** SyncAdapter Instantiation that any anonymous syncs call. */
- private final AnonymousSyncAdapterImpl mSyncAdapter = new AnonymousSyncAdapterImpl();
-
- /** Keep track of on-going syncs, keyed by tag. */
- @GuardedBy("mLock")
- private final HashMap<Bundle, AnonymousSyncThread>
- mSyncThreads = new HashMap<Bundle, AnonymousSyncThread>();
- /** Lock object for accessing the SyncThreads HashMap. */
- private final Object mSyncThreadLock = new Object();
-
- @Override
- public IBinder onBind(Intent intent) {
- return mSyncAdapter.asBinder();
- }
-
- /** {@hide} */
- private class AnonymousSyncAdapterImpl extends IAnonymousSyncAdapter.Stub {
-
- @Override
- public void startSync(ISyncContext syncContext, Bundle extras) {
- // Wrap the provided Sync Context because it may go away by the time
- // we call it.
- final SyncContext syncContextClient = new SyncContext(syncContext);
- boolean alreadyInProgress = false;
- synchronized (mSyncThreadLock) {
- if (mSyncThreads.containsKey(extras)) {
- // Don't want to call back to SyncManager while still
- // holding lock.
- alreadyInProgress = true;
- } else {
- AnonymousSyncThread syncThread = new AnonymousSyncThread(
- syncContextClient, extras);
- mSyncThreads.put(extras, syncThread);
- syncThread.start();
- }
- }
- if (alreadyInProgress) {
- syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
- }
- }
-
- /**
- * Used by the SM to cancel a specific sync using the {@link
- * com.android.server.content.SyncManager.ActiveSyncContext} as a handle.
- */
- @Override
- public void cancelSync(ISyncContext syncContext) {
- AnonymousSyncThread runningSync = null;
- synchronized (mSyncThreadLock) {
- for (AnonymousSyncThread thread : mSyncThreads.values()) {
- if (thread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
- runningSync = thread;
- break;
- }
- }
- }
- if (runningSync != null) {
- runningSync.interrupt();
- }
- }
- }
-
- /**
- * {@hide}
- * Similar to {@link android.content.AbstractThreadedSyncAdapter.SyncThread}. However while
- * the ATSA considers an already in-progress sync to be if the account provided is currently
- * syncing, this anonymous sync has no notion of account and therefore considers a sync unique
- * if the provided bundle is different.
- */
- private class AnonymousSyncThread extends Thread {
- private final SyncContext mSyncContext;
- private final Bundle mExtras;
-
- public AnonymousSyncThread(SyncContext syncContext, Bundle extras) {
- mSyncContext = syncContext;
- mExtras = extras;
- }
-
- @Override
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-
- Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, getApplication().getPackageName());
-
- SyncResult syncResult = new SyncResult();
- try {
- if (isCancelled()) {
- return;
- }
- // Run the sync based off of the provided code.
- SyncService.this.onPerformSync(mExtras, syncResult);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
- if (!isCancelled()) {
- mSyncContext.onFinished(syncResult);
- }
- // Synchronize so that the assignment will be seen by other
- // threads
- // that also synchronize accesses to mSyncThreads.
- synchronized (mSyncThreadLock) {
- mSyncThreads.remove(mExtras);
- }
- }
- }
-
- private boolean isCancelled() {
- return Thread.currentThread().isInterrupted();
- }
- }
-
- /**
- * Initiate an anonymous sync using this service. SyncAdapter-specific
- * parameters may be specified in extras, which is guaranteed to not be
- * null.
- */
- public abstract void onPerformSync(Bundle extras, SyncResult syncResult);
-
-}
diff --git a/core/java/android/ddm/DdmHandleProfiling.java b/core/java/android/ddm/DdmHandleProfiling.java
index e1d359c..ec08393 100644
--- a/core/java/android/ddm/DdmHandleProfiling.java
+++ b/core/java/android/ddm/DdmHandleProfiling.java
@@ -186,7 +186,7 @@ public class DdmHandleProfiling extends ChunkHandler {
* Handle a "Method PRofiling Query" request.
*/
private Chunk handleMPRQ(Chunk request) {
- int result = Debug.isMethodTracingActive() ? 1 : 0;
+ int result = Debug.getMethodTracingMode();
/* create a non-empty reply so the handler fires on completion */
byte[] reply = { (byte) result };
diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
index b83911a..41c6603 100644
--- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -105,8 +105,12 @@ public final class ApduServiceInfo implements Parcelable {
if (onHost) {
parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA);
if (parser == null) {
- throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
- " meta-data");
+ Log.d(TAG, "Didn't find service meta-data, trying legacy.");
+ parser = si.loadXmlMetaData(pm, HostApduService.OLD_SERVICE_META_DATA);
+ if (parser == null) {
+ throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA +
+ " meta-data");
+ }
}
} else {
parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA);
@@ -170,12 +174,12 @@ public final class ApduServiceInfo implements Parcelable {
com.android.internal.R.styleable.AidGroup_description);
String groupCategory = groupAttrs.getString(
com.android.internal.R.styleable.AidGroup_category);
- if (!CardEmulationManager.CATEGORY_PAYMENT.equals(groupCategory)) {
- groupCategory = CardEmulationManager.CATEGORY_OTHER;
+ if (!CardEmulation.CATEGORY_PAYMENT.equals(groupCategory)) {
+ groupCategory = CardEmulation.CATEGORY_OTHER;
}
currentGroup = mCategoryToGroup.get(groupCategory);
if (currentGroup != null) {
- if (!CardEmulationManager.CATEGORY_OTHER.equals(groupCategory)) {
+ if (!CardEmulation.CATEGORY_OTHER.equals(groupCategory)) {
Log.e(TAG, "Not allowing multiple aid-groups in the " +
groupCategory + " category");
currentGroup = null;
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
new file mode 100644
index 0000000..3cd7863
--- /dev/null
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.nfc.cardemulation;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.ActivityThread;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.nfc.INfcCardEmulation;
+import android.nfc.NfcAdapter;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+
+public final class CardEmulation {
+ static final String TAG = "CardEmulation";
+
+ /**
+ * Activity action: ask the user to change the default
+ * card emulation service for a certain category. This will
+ * show a dialog that asks the user whether he wants to
+ * replace the current default service with the service
+ * identified with the ComponentName specified in
+ * {@link #EXTRA_SERVICE_COMPONENT}, for the category
+ * specified in {@link #EXTRA_CATEGORY}
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CHANGE_DEFAULT =
+ "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
+
+ /**
+ * The category extra for {@link #ACTION_CHANGE_DEFAULT}
+ *
+ * @see #ACTION_CHANGE_DEFAULT
+ */
+ public static final String EXTRA_CATEGORY = "category";
+
+ /**
+ * The ComponentName object passed in as a parcelable
+ * extra for {@link #ACTION_CHANGE_DEFAULT}
+ *
+ * @see #ACTION_CHANGE_DEFAULT
+ */
+ public static final String EXTRA_SERVICE_COMPONENT = "component";
+
+ /**
+ * The payment category can be used to indicate that an AID
+ * represents a payment application.
+ */
+ public static final String CATEGORY_PAYMENT = "payment";
+
+ /**
+ * If an AID group does not contain a category, or the
+ * specified category is not defined by the platform version
+ * that is parsing the AID group, all AIDs in the group will
+ * automatically be categorized under the {@link #CATEGORY_OTHER}
+ * category.
+ */
+ public static final String CATEGORY_OTHER = "other";
+
+ /**
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, the user has set a default service for this
+ * AID category. If a remote reader selects any of the AIDs
+ * that the default service has registered in this category,
+ * that service will automatically be bound to to handle
+ * the transaction.
+ *
+ * <p>There are still cases where a service that is
+ * not the default for a category can selected:
+ * <p>
+ * If a remote reader selects an AID in this category
+ * that is not handled by the default service, and there is a set
+ * of other services {S} that do handle this AID, the
+ * user is asked if he wants to use any of the services in
+ * {S} instead.
+ * <p>
+ * As a special case, if the size of {S} is one, containing a single service X,
+ * and all AIDs X has registered in this category are not
+ * registered by any other service, then X will be
+ * selected automatically without asking the user.
+ * <p>Example:
+ * <ul>
+ * <li>Service A registers AIDs "1", "2" and "3" in the category
+ * <li>Service B registers AIDs "3" and "4" in the category
+ * <li>Service C registers AIDs "5" and "6" in the category
+ * </ul>
+ * In this case, the following will happen when service A
+ * is the default:
+ * <ul>
+ * <li>Reader selects AID "1", "2" or "3": service A is invoked automatically
+ * <li>Reader selects AID "4": the user is asked to confirm he
+ * wants to use service B, because its AIDs overlap with service A.
+ * <li>Reader selects AID "5" or "6": service C is invoked automatically,
+ * because all AIDs it has asked for are only registered by C,
+ * and there is no overlap.
+ * </ul>
+ *
+ */
+ public static final int SELECTION_MODE_PREFER_DEFAULT = 0;
+
+ /**
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, whenever an AID of this category is selected,
+ * the user is asked which service he wants to use to handle
+ * the transaction, even if there is only one matching service.
+ */
+ public static final int SELECTION_MODE_ALWAYS_ASK = 1;
+
+ /**
+ * Return value for {@link #getSelectionModeForCategory(String)}.
+ *
+ * <p>In this mode, the user will only be asked to select a service
+ * if the selected AID has been registered by multiple applications.
+ */
+ public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
+
+ static boolean sIsInitialized = false;
+ static HashMap<Context, CardEmulation> sCardEmus = new HashMap();
+ static INfcCardEmulation sService;
+
+ final Context mContext;
+
+ private CardEmulation(Context context, INfcCardEmulation service) {
+ mContext = context.getApplicationContext();
+ sService = service;
+ }
+
+ public static synchronized CardEmulation getInstance(NfcAdapter adapter) {
+ if (adapter == null) throw new NullPointerException("NfcAdapter is null");
+ Context context = adapter.getContext();
+ if (context == null) {
+ Log.e(TAG, "NfcAdapter context is null.");
+ throw new UnsupportedOperationException();
+ }
+ if (!sIsInitialized) {
+ IPackageManager pm = ActivityThread.getPackageManager();
+ if (pm == null) {
+ Log.e(TAG, "Cannot get PackageManager");
+ throw new UnsupportedOperationException();
+ }
+ try {
+ if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
+ Log.e(TAG, "This device does not support card emulation");
+ throw new UnsupportedOperationException();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "PackageManager query failed.");
+ throw new UnsupportedOperationException();
+ }
+ sIsInitialized = true;
+ }
+ CardEmulation manager = sCardEmus.get(context);
+ if (manager == null) {
+ // Get card emu service
+ INfcCardEmulation service = adapter.getCardEmulationService();
+ manager = new CardEmulation(context, service);
+ sCardEmus.put(context, manager);
+ }
+ return manager;
+ }
+
+ /**
+ * Allows an application to query whether a service is currently
+ * the default service to handle a card emulation category.
+ *
+ * <p>Note that if {@link #getSelectionModeForCategory(String)}
+ * returns {@link #SELECTION_MODE_ALWAYS_ASK}, this method will always
+ * return false.
+ *
+ * @param service The ComponentName of the service
+ * @param category The category
+ * @return whether service is currently the default service for the category.
+ */
+ public boolean isDefaultServiceForCategory(ComponentName service, String category) {
+ try {
+ return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service,
+ category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ *
+ * Allows an application to query whether a service is currently
+ * the default handler for a specified ISO7816-4 Application ID.
+ *
+ * @param service The ComponentName of the service
+ * @param aid The ISO7816-4 Application ID
+ * @return
+ */
+ public boolean isDefaultServiceForAid(ComponentName service, String aid) {
+ try {
+ return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Returns the application selection mode for the passed in category.
+ * Valid return values are:
+ * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default
+ * application for this category, which will be preferred.
+ * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked
+ * every time what app he would like to use in this category.
+ * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked
+ * to pick a service if there is a conflict.
+ * @param category The category, for example {@link #CATEGORY_PAYMENT}
+ * @return
+ */
+ public int getSelectionModeForCategory(String category) {
+ if (CATEGORY_PAYMENT.equals(category)) {
+ String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
+ if (defaultComponent != null) {
+ return SELECTION_MODE_PREFER_DEFAULT;
+ } else {
+ return SELECTION_MODE_ALWAYS_ASK;
+ }
+ } else {
+ // All other categories are in "only ask if conflict" mode
+ return SELECTION_MODE_ASK_IF_CONFLICT;
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean setDefaultServiceForCategory(ComponentName service, String category) {
+ try {
+ return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service, category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service,
+ category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean setDefaultForNextTap(ComponentName service) {
+ try {
+ return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.setDefaultForNextTap(UserHandle.myUserId(), service);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+ /**
+ * @hide
+ */
+ public List<ApduServiceInfo> getServices(String category) {
+ try {
+ return sService.getServices(UserHandle.myUserId(), category);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return null;
+ }
+ try {
+ return sService.getServices(UserHandle.myUserId(), category);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return null;
+ }
+ }
+ }
+
+ void recoverService() {
+ NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext);
+ sService = adapter.getCardEmulationService();
+ }
+}
diff --git a/core/java/android/nfc/cardemulation/CardEmulationManager.java b/core/java/android/nfc/cardemulation/CardEmulationManager.java
index 9d60c73..124ea1c 100644
--- a/core/java/android/nfc/cardemulation/CardEmulationManager.java
+++ b/core/java/android/nfc/cardemulation/CardEmulationManager.java
@@ -33,6 +33,10 @@ import android.util.Log;
import java.util.HashMap;
import java.util.List;
+/**
+ * TODO Remove when calling .apks are upgraded
+ * @hide
+ */
public final class CardEmulationManager {
static final String TAG = "CardEmulationManager";
diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java
index ae94b2f..1bb2ea4 100644
--- a/core/java/android/nfc/cardemulation/HostApduService.java
+++ b/core/java/android/nfc/cardemulation/HostApduService.java
@@ -40,13 +40,31 @@ public abstract class HostApduService extends Service {
*/
@SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
+ "android.nfc.cardemulation.action.HOST_APDU_SERVICE";
+
+ /**
+ * The name of the meta-data element that contains
+ * more information about this service.
+ */
+ public static final String SERVICE_META_DATA =
+ "android.nfc.cardemulation.host_apdu_service";
+
+ /**
+ * The {@link Intent} that must be declared as handled by the service.
+ * TODO Remove
+ * @hide
+ */
+ public static final String OLD_SERVICE_INTERFACE =
"android.nfc.HostApduService";
/**
* The name of the meta-data element that contains
* more information about this service.
+ *
+ * TODO Remove
+ * @hide
*/
- public static final String SERVICE_META_DATA = "android.nfc.HostApduService";
+ public static final String OLD_SERVICE_META_DATA = "android.nfc.HostApduService";
/**
* Reason for {@link #onDeactivated(int)}.
diff --git a/core/java/android/nfc/cardemulation/OffHostApduService.java b/core/java/android/nfc/cardemulation/OffHostApduService.java
index 79599db..15f63f9 100644
--- a/core/java/android/nfc/cardemulation/OffHostApduService.java
+++ b/core/java/android/nfc/cardemulation/OffHostApduService.java
@@ -42,13 +42,14 @@ public abstract class OffHostApduService extends Service {
*/
@SdkConstant(SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
- "android.nfc.OffHostApduService";
+ "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE";
/**
* The name of the meta-data element that contains
* more information about this service.
*/
- public static final String SERVICE_META_DATA = "android.nfc.OffHostApduService";
+ public static final String SERVICE_META_DATA =
+ "android.nfc.cardemulation.off_host_apdu_service";
/**
* The Android platform itself will not bind to this service,
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index b78bbc3..60ce132 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -645,11 +645,13 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
}
/**
- * Determine whether method tracing is currently active.
+ * Determine whether method tracing is currently active and what type is
+ * active.
+ *
* @hide
*/
- public static boolean isMethodTracingActive() {
- return VMDebug.isMethodTracingActive();
+ public static int getMethodTracingMode() {
+ return VMDebug.getMethodTracingMode();
}
/**
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index ebb7eb8..f445fd5 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -19,7 +19,6 @@ package android.provider;
import static android.net.TrafficStats.KB_IN_BYTES;
import static libcore.io.OsConstants.SEEK_SET;
-import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -30,16 +29,13 @@ import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Parcel;
+import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
-import android.os.Parcelable;
import android.util.Log;
-import com.android.internal.util.Preconditions;
import com.google.android.collect.Lists;
import libcore.io.ErrnoException;
@@ -62,9 +58,12 @@ import java.util.List;
public final class DocumentsContract {
private static final String TAG = "Documents";
- // content://com.example/docs/12/
- // content://com.example/docs/12/children/
- // content://com.example/docs/12/search/?query=pony
+ // content://com.example/root/
+ // content://com.example/root/sdcard/
+ // content://com.example/root/sdcard/recent/
+ // content://com.example/document/12/
+ // content://com.example/document/12/children/
+ // content://com.example/document/12/search/?query=pony
private DocumentsContract() {
}
@@ -75,279 +74,297 @@ public final class DocumentsContract {
/** {@hide} */
public static final String ACTION_MANAGE_DOCUMENTS = "android.provider.action.MANAGE_DOCUMENTS";
- /** {@hide} */
- public static final String
- ACTION_DOCUMENT_ROOT_CHANGED = "android.provider.action.DOCUMENT_ROOT_CHANGED";
-
/**
- * Constants for individual documents.
+ * Constants related to a document, including {@link Cursor} columns names
+ * and flags.
+ * <p>
+ * A document can be either an openable file (with a specific MIME type), or
+ * a directory containing additional documents (with the
+ * {@link #MIME_TYPE_DIR} MIME type).
+ * <p>
+ * All columns are <em>read-only</em> to client applications.
*/
- public final static class Documents {
- private Documents() {
+ public final static class Document {
+ private Document() {
}
/**
- * MIME type of a document which is a directory that may contain additional
- * documents.
+ * Unique ID of a document. This ID is both provided by and interpreted
+ * by a {@link DocumentsProvider}, and should be treated as an opaque
+ * value by client applications.
+ * <p>
+ * Each document must have a unique ID within a provider, but that
+ * single document may be included as a child of multiple directories.
+ * <p>
+ * A provider must always return durable IDs, since they will be used to
+ * issue long-term Uri permission grants when an application interacts
+ * with {@link Intent#ACTION_OPEN_DOCUMENT} and
+ * {@link Intent#ACTION_CREATE_DOCUMENT}.
+ * <p>
+ * Type: STRING
*/
- public static final String MIME_TYPE_DIR = "vnd.android.doc/dir";
+ public static final String COLUMN_DOCUMENT_ID = "document_id";
/**
- * Flag indicating that a document is a directory that supports creation of
- * new files within it.
+ * Concrete MIME type of a document. For example, "image/png" or
+ * "application/pdf" for openable files. A document can also be a
+ * directory containing additional documents, which is represented with
+ * the {@link #MIME_TYPE_DIR} MIME type.
+ * <p>
+ * Type: STRING
*
- * @see DocumentColumns#FLAGS
+ * @see #MIME_TYPE_DIR
*/
- public static final int FLAG_SUPPORTS_CREATE = 1;
+ public static final String COLUMN_MIME_TYPE = "mime_type";
/**
- * Flag indicating that a document is renamable.
+ * Display name of a document, used as the primary title displayed to a
+ * user.
+ * <p>
+ * Type: STRING
+ */
+ public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
+
+ /**
+ * Summary of a document, which may be shown to a user. The summary may
+ * be {@code null}.
+ * <p>
+ * Type: STRING
+ */
+ public static final String COLUMN_SUMMARY = "summary";
+
+ /**
+ * Timestamp when a document was last modified, in milliseconds since
+ * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A
+ * {@link DocumentsProvider} can update this field using events from
+ * {@link OnCloseListener} or other reliable
+ * {@link ParcelFileDescriptor} transports.
+ * <p>
+ * Type: INTEGER (long)
*
- * @see DocumentColumns#FLAGS
+ * @see System#currentTimeMillis()
*/
- public static final int FLAG_SUPPORTS_RENAME = 1 << 1;
+ public static final String COLUMN_LAST_MODIFIED = "last_modified";
/**
- * Flag indicating that a document is deletable.
+ * Specific icon resource ID for a document, or {@code null} to use
+ * platform default icon based on {@link #COLUMN_MIME_TYPE}.
+ * <p>
+ * Type: INTEGER (int)
+ */
+ public static final String COLUMN_ICON = "icon";
+
+ /**
+ * Flags that apply to a document.
+ * <p>
+ * Type: INTEGER (int)
*
- * @see DocumentColumns#FLAGS
+ * @see #FLAG_SUPPORTS_WRITE
+ * @see #FLAG_SUPPORTS_DELETE
+ * @see #FLAG_SUPPORTS_THUMBNAIL
+ * @see #FLAG_DIR_PREFERS_GRID
+ * @see #FLAG_DIR_SUPPORTS_CREATE
+ * @see #FLAG_DIR_SUPPORTS_SEARCH
*/
- public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
+ public static final String COLUMN_FLAGS = "flags";
/**
- * Flag indicating that a document can be represented as a thumbnail.
+ * Size of a document, in bytes, or {@code null} if unknown.
+ * <p>
+ * Type: INTEGER (long)
+ */
+ public static final String COLUMN_SIZE = OpenableColumns.SIZE;
+
+ /**
+ * MIME type of a document which is a directory that may contain
+ * additional documents.
*
- * @see DocumentColumns#FLAGS
+ * @see #COLUMN_MIME_TYPE
*/
- public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3;
+ public static final String MIME_TYPE_DIR = "vnd.android.document/directory";
/**
- * Flag indicating that a document is a directory that supports search.
+ * Flag indicating that a document can be represented as a thumbnail.
*
- * @see DocumentColumns#FLAGS
+ * @see #COLUMN_FLAGS
+ * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri,
+ * Point, CancellationSignal)
+ * @see DocumentsProvider#openDocumentThumbnail(String, Point,
+ * android.os.CancellationSignal)
*/
- public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
+ public static final int FLAG_SUPPORTS_THUMBNAIL = 1;
/**
* Flag indicating that a document supports writing.
+ * <p>
+ * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT},
+ * the calling application is granted both
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
+ * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual
+ * writability of a document may change over time, for example due to
+ * remote access changes. This flag indicates that a document client can
+ * expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
*
- * @see DocumentColumns#FLAGS
+ * @see #COLUMN_FLAGS
*/
- public static final int FLAG_SUPPORTS_WRITE = 1 << 5;
+ public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
/**
- * Flag indicating that a document is a directory that prefers its contents
- * be shown in a larger format grid. Usually suitable when a directory
- * contains mostly pictures.
+ * Flag indicating that a document is deletable.
*
- * @see DocumentColumns#FLAGS
+ * @see #COLUMN_FLAGS
+ * @see DocumentsContract#deleteDocument(ContentResolver, Uri)
+ * @see DocumentsProvider#deleteDocument(String)
*/
- public static final int FLAG_PREFERS_GRID = 1 << 6;
- }
-
- /**
- * Extra boolean flag included in a directory {@link Cursor#getExtras()}
- * indicating that a document provider is still loading data. For example, a
- * provider has returned some results, but is still waiting on an
- * outstanding network request.
- *
- * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
- * boolean)
- */
- public static final String EXTRA_LOADING = "loading";
-
- /**
- * Extra string included in a directory {@link Cursor#getExtras()}
- * providing an informational message that should be shown to a user. For
- * example, a provider may wish to indicate that not all documents are
- * available.
- */
- public static final String EXTRA_INFO = "info";
-
- /**
- * Extra string included in a directory {@link Cursor#getExtras()} providing
- * an error message that should be shown to a user. For example, a provider
- * may wish to indicate that a network error occurred. The user may choose
- * to retry, resulting in a new query.
- */
- public static final String EXTRA_ERROR = "error";
-
- /** {@hide} */
- public static final String METHOD_GET_ROOTS = "android:getRoots";
- /** {@hide} */
- public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
- /** {@hide} */
- public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument";
- /** {@hide} */
- public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
-
- /** {@hide} */
- public static final String EXTRA_AUTHORITY = "authority";
- /** {@hide} */
- public static final String EXTRA_PACKAGE_NAME = "packageName";
- /** {@hide} */
- public static final String EXTRA_URI = "uri";
- /** {@hide} */
- public static final String EXTRA_ROOTS = "roots";
- /** {@hide} */
- public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
-
- private static final String PATH_DOCS = "docs";
- private static final String PATH_CHILDREN = "children";
- private static final String PATH_SEARCH = "search";
-
- private static final String PARAM_QUERY = "query";
+ public static final int FLAG_SUPPORTS_DELETE = 1 << 2;
- /**
- * Build Uri representing the given {@link DocumentColumns#DOC_ID} in a
- * document provider.
- */
- public static Uri buildDocumentUri(String authority, String docId) {
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
- .authority(authority).appendPath(PATH_DOCS).appendPath(docId).build();
- }
+ /**
+ * Flag indicating that a document is a directory that supports creation
+ * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is
+ * {@link #MIME_TYPE_DIR}.
+ *
+ * @see #COLUMN_FLAGS
+ * @see DocumentsContract#createDocument(ContentResolver, Uri, String,
+ * String)
+ * @see DocumentsProvider#createDocument(String, String, String)
+ */
+ public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3;
- /**
- * Build Uri representing the contents of the given directory in a document
- * provider. The given document must be {@link Documents#MIME_TYPE_DIR}.
- *
- * @hide
- */
- public static Uri buildChildrenUri(String authority, String docId) {
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
- .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_CHILDREN).build();
- }
+ /**
+ * Flag indicating that a directory supports search. Only valid when
+ * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
+ *
+ * @see #COLUMN_FLAGS
+ * @see DocumentsProvider#querySearchDocuments(String, String,
+ * String[])
+ */
+ public static final int FLAG_DIR_SUPPORTS_SEARCH = 1 << 4;
- /**
- * Build Uri representing a search for matching documents under a specific
- * directory in a document provider. The given document must have
- * {@link Documents#FLAG_SUPPORTS_SEARCH}.
- *
- * @hide
- */
- public static Uri buildSearchUri(String authority, String docId, String query) {
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
- .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_SEARCH)
- .appendQueryParameter(PARAM_QUERY, query).build();
+ /**
+ * Flag indicating that a directory prefers its contents be shown in a
+ * larger format grid. Usually suitable when a directory contains mostly
+ * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is
+ * {@link #MIME_TYPE_DIR}.
+ *
+ * @see #COLUMN_FLAGS
+ */
+ public static final int FLAG_DIR_PREFERS_GRID = 1 << 5;
}
/**
- * Extract the {@link DocumentColumns#DOC_ID} from the given Uri.
+ * Constants related to a root of documents, including {@link Cursor}
+ * columns names and flags.
+ * <p>
+ * All columns are <em>read-only</em> to client applications.
*/
- public static String getDocId(Uri documentUri) {
- final List<String> paths = documentUri.getPathSegments();
- if (paths.size() < 2) {
- throw new IllegalArgumentException("Not a document: " + documentUri);
+ public final static class Root {
+ private Root() {
}
- if (!PATH_DOCS.equals(paths.get(0))) {
- throw new IllegalArgumentException("Not a document: " + documentUri);
- }
- return paths.get(1);
- }
-
- /** {@hide} */
- public static String getSearchQuery(Uri documentUri) {
- return documentUri.getQueryParameter(PARAM_QUERY);
- }
- /**
- * Standard columns for document queries. Document providers <em>must</em>
- * support at least these columns when queried.
- */
- public interface DocumentColumns extends OpenableColumns {
/**
- * Unique ID for a document. Values <em>must</em> never change once
- * returned, since they may used for long-term Uri permission grants.
+ * Unique ID of a root. This ID is both provided by and interpreted by a
+ * {@link DocumentsProvider}, and should be treated as an opaque value
+ * by client applications.
* <p>
* Type: STRING
*/
- public static final String DOC_ID = "doc_id";
+ public static final String COLUMN_ROOT_ID = "root_id";
/**
- * MIME type of a document.
+ * Type of a root, used for clustering when presenting multiple roots to
+ * a user.
* <p>
- * Type: STRING
+ * Type: INTEGER (int)
*
- * @see Documents#MIME_TYPE_DIR
+ * @see #ROOT_TYPE_SERVICE
+ * @see #ROOT_TYPE_SHORTCUT
+ * @see #ROOT_TYPE_DEVICE
*/
- public static final String MIME_TYPE = "mime_type";
+ public static final String COLUMN_ROOT_TYPE = "root_type";
/**
- * Timestamp when a document was last modified, in milliseconds since
- * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. Document
- * providers can update this field using events from
- * {@link OnCloseListener} or other reliable
- * {@link ParcelFileDescriptor} transports.
+ * Flags that apply to a root.
* <p>
- * Type: INTEGER (long)
+ * Type: INTEGER (int)
*
- * @see System#currentTimeMillis()
+ * @see #FLAG_LOCAL_ONLY
+ * @see #FLAG_SUPPORTS_CREATE
+ * @see #FLAG_ADVANCED
+ * @see #FLAG_PROVIDES_AUDIO
+ * @see #FLAG_PROVIDES_IMAGES
+ * @see #FLAG_PROVIDES_VIDEO
*/
- public static final String LAST_MODIFIED = "last_modified";
+ public static final String COLUMN_FLAGS = "flags";
/**
- * Specific icon resource for a document, or {@code null} to resolve
- * default using {@link #MIME_TYPE}.
+ * Icon resource ID for a root.
* <p>
* Type: INTEGER (int)
*/
- public static final String ICON = "icon";
+ public static final String COLUMN_ICON = "icon";
/**
- * Summary for a document, or {@code null} to omit.
+ * Title for a root, which will be shown to a user.
* <p>
* Type: STRING
*/
- public static final String SUMMARY = "summary";
+ public static final String COLUMN_TITLE = "title";
/**
- * Flags that apply to a specific document.
+ * Summary for this root, which may be shown to a user. The summary may
+ * be {@code null}.
* <p>
- * Type: INTEGER (int)
+ * Type: STRING
*/
- public static final String FLAGS = "flags";
- }
+ public static final String COLUMN_SUMMARY = "summary";
- /**
- * Metadata about a specific root of documents.
- */
- public final static class DocumentRoot implements Parcelable {
/**
- * Root that represents a storage service, such as a cloud-based
- * service.
+ * Document which is a directory that represents the top directory of
+ * this root.
+ * <p>
+ * Type: STRING
*
- * @see #rootType
+ * @see Document#COLUMN_DOCUMENT_ID
*/
- public static final int ROOT_TYPE_SERVICE = 1;
+ public static final String COLUMN_DOCUMENT_ID = "document_id";
+
+ /**
+ * Number of bytes available in this root, or {@code null} if unknown or
+ * unbounded.
+ * <p>
+ * Type: INTEGER (long)
+ */
+ public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
/**
- * Root that represents a shortcut to content that may be available
- * elsewhere through another storage root.
+ * Type of root that represents a storage service, such as a cloud-based
+ * service.
*
- * @see #rootType
+ * @see #COLUMN_ROOT_TYPE
*/
- public static final int ROOT_TYPE_SHORTCUT = 2;
+ public static final int ROOT_TYPE_SERVICE = 1;
/**
- * Root that represents a physical storage device.
+ * Type of root that represents a shortcut to content that may be
+ * available elsewhere through another storage root.
*
- * @see #rootType
+ * @see #COLUMN_ROOT_TYPE
*/
- public static final int ROOT_TYPE_DEVICE = 3;
+ public static final int ROOT_TYPE_SHORTCUT = 2;
/**
- * Root that represents a physical storage device that should only be
- * displayed to advanced users.
+ * Type of root that represents a physical storage device.
*
- * @see #rootType
+ * @see #COLUMN_ROOT_TYPE
*/
- public static final int ROOT_TYPE_DEVICE_ADVANCED = 4;
+ public static final int ROOT_TYPE_DEVICE = 3;
/**
* Flag indicating that at least one directory under this root supports
- * creating content.
+ * creating content. Roots with this flag will be shown when an
+ * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
*
- * @see #flags
+ * @see #COLUMN_FLAGS
*/
public static final int FLAG_SUPPORTS_CREATE = 1;
@@ -355,138 +372,210 @@ public final class DocumentsContract {
* Flag indicating that this root offers content that is strictly local
* on the device. That is, no network requests are made for the content.
*
- * @see #flags
+ * @see #COLUMN_FLAGS
+ * @see Intent#EXTRA_LOCAL_ONLY
*/
public static final int FLAG_LOCAL_ONLY = 1 << 1;
- /** {@hide} */
- public String authority;
-
/**
- * Root type, use for clustering.
+ * Flag indicating that this root should only be visible to advanced
+ * users.
*
- * @see #ROOT_TYPE_SERVICE
- * @see #ROOT_TYPE_DEVICE
+ * @see #COLUMN_FLAGS
*/
- public int rootType;
+ public static final int FLAG_ADVANCED = 1 << 2;
/**
- * Flags for this root.
+ * Flag indicating that a root offers audio documents. When a user is
+ * selecting audio, roots not providing audio may be excluded.
*
- * @see #FLAG_LOCAL_ONLY
+ * @see #COLUMN_FLAGS
+ * @see Intent#EXTRA_MIME_TYPES
*/
- public int flags;
+ public static final int FLAG_PROVIDES_AUDIO = 1 << 3;
/**
- * Icon resource ID for this root.
- */
- public int icon;
-
- /**
- * Title for this root.
- */
- public String title;
-
- /**
- * Summary for this root. May be {@code null}.
+ * Flag indicating that a root offers video documents. When a user is
+ * selecting video, roots not providing video may be excluded.
+ *
+ * @see #COLUMN_FLAGS
+ * @see Intent#EXTRA_MIME_TYPES
*/
- public String summary;
+ public static final int FLAG_PROVIDES_VIDEO = 1 << 4;
/**
- * Document which is a directory that represents the top of this root.
- * Must not be {@code null}.
+ * Flag indicating that a root offers image documents. When a user is
+ * selecting images, roots not providing images may be excluded.
*
- * @see DocumentColumns#DOC_ID
+ * @see #COLUMN_FLAGS
+ * @see Intent#EXTRA_MIME_TYPES
*/
- public String docId;
+ public static final int FLAG_PROVIDES_IMAGES = 1 << 5;
/**
- * Document which is a directory representing recently modified
- * documents under this root. This directory should return at most two
- * dozen documents modified within the last 90 days. May be {@code null}
- * if this root doesn't support recents.
+ * Flag indicating that this root can report recently modified
+ * documents.
*
- * @see DocumentColumns#DOC_ID
+ * @see #COLUMN_FLAGS
+ * @see DocumentsContract#buildRecentDocumentsUri(String, String)
*/
- public String recentDocId;
+ public static final int FLAG_SUPPORTS_RECENTS = 1 << 6;
+ }
- /**
- * Number of free bytes of available in this root, or -1 if unknown or
- * unbounded.
- */
- public long availableBytes;
+ /**
+ * Optional boolean flag included in a directory {@link Cursor#getExtras()}
+ * indicating that a document provider is still loading data. For example, a
+ * provider has returned some results, but is still waiting on an
+ * outstanding network request. The provider must send a content changed
+ * notification when loading is finished.
+ *
+ * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver,
+ * boolean)
+ */
+ public static final String EXTRA_LOADING = "loading";
- /**
- * Set of MIME type filters describing the content offered by this root,
- * or {@code null} to indicate that all MIME types are supported. For
- * example, a provider only supporting audio and video might set this to
- * {@code ["audio/*", "video/*"]}.
- */
- public String[] mimeTypes;
+ /**
+ * Optional string included in a directory {@link Cursor#getExtras()}
+ * providing an informational message that should be shown to a user. For
+ * example, a provider may wish to indicate that not all documents are
+ * available.
+ */
+ public static final String EXTRA_INFO = "info";
- public DocumentRoot() {
- }
+ /**
+ * Optional string included in a directory {@link Cursor#getExtras()}
+ * providing an error message that should be shown to a user. For example, a
+ * provider may wish to indicate that a network error occurred. The user may
+ * choose to retry, resulting in a new query.
+ */
+ public static final String EXTRA_ERROR = "error";
- /** {@hide} */
- public DocumentRoot(Parcel in) {
- rootType = in.readInt();
- flags = in.readInt();
- icon = in.readInt();
- title = in.readString();
- summary = in.readString();
- docId = in.readString();
- recentDocId = in.readString();
- availableBytes = in.readLong();
- mimeTypes = in.readStringArray();
- }
+ /** {@hide} */
+ public static final String METHOD_CREATE_DOCUMENT = "android:createDocument";
+ /** {@hide} */
+ public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
- /** {@hide} */
- public Drawable loadIcon(Context context) {
- if (icon != 0) {
- if (authority != null) {
- final PackageManager pm = context.getPackageManager();
- final ProviderInfo info = pm.resolveContentProvider(authority, 0);
- if (info != null) {
- return pm.getDrawable(info.packageName, icon, info.applicationInfo);
- }
- } else {
- return context.getResources().getDrawable(icon);
- }
- }
- return null;
- }
+ /** {@hide} */
+ public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size";
- @Override
- public int describeContents() {
- return 0;
- }
+ private static final String PATH_ROOT = "root";
+ private static final String PATH_RECENT = "recent";
+ private static final String PATH_DOCUMENT = "document";
+ private static final String PATH_CHILDREN = "children";
+ private static final String PATH_SEARCH = "search";
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- Preconditions.checkNotNull(docId);
-
- dest.writeInt(rootType);
- dest.writeInt(flags);
- dest.writeInt(icon);
- dest.writeString(title);
- dest.writeString(summary);
- dest.writeString(docId);
- dest.writeString(recentDocId);
- dest.writeLong(availableBytes);
- dest.writeStringArray(mimeTypes);
+ private static final String PARAM_QUERY = "query";
+
+ /**
+ * Build Uri representing the roots of a document provider. When queried, a
+ * provider will return one or more rows with columns defined by
+ * {@link Root}.
+ *
+ * @see DocumentsProvider#queryRoots(String[])
+ */
+ public static Uri buildRootsUri(String authority) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).appendPath(PATH_ROOT).build();
+ }
+
+ /**
+ * Build Uri representing the recently modified documents of a specific
+ * root. When queried, a provider will return zero or more rows with columns
+ * defined by {@link Document}.
+ *
+ * @see DocumentsProvider#queryRecentDocuments(String, String[])
+ * @see #getRootId(Uri)
+ */
+ public static Uri buildRecentDocumentsUri(String authority, String rootId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).appendPath(PATH_ROOT).appendPath(rootId)
+ .appendPath(PATH_RECENT).build();
+ }
+
+ /**
+ * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a
+ * document provider. When queried, a provider will return a single row with
+ * columns defined by {@link Document}.
+ *
+ * @see DocumentsProvider#queryDocument(String, String[])
+ * @see #getDocumentId(Uri)
+ */
+ public static Uri buildDocumentUri(String authority, String documentId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build();
+ }
+
+ /**
+ * Build Uri representing the children of the given directory in a document
+ * provider. When queried, a provider will return zero or more rows with
+ * columns defined by {@link Document}.
+ *
+ * @param parentDocumentId the document to return children for, which must
+ * be a directory with MIME type of
+ * {@link Document#MIME_TYPE_DIR}.
+ * @see DocumentsProvider#queryChildDocuments(String, String[], String)
+ * @see #getDocumentId(Uri)
+ */
+ public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+ .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN)
+ .build();
+ }
+
+ /**
+ * Build Uri representing a search for matching documents under a specific
+ * directory in a document provider. When queried, a provider will return
+ * zero or more rows with columns defined by {@link Document}.
+ *
+ * @param parentDocumentId the document to return children for, which must
+ * be both a directory with MIME type of
+ * {@link Document#MIME_TYPE_DIR} and have
+ * {@link Document#FLAG_DIR_SUPPORTS_SEARCH} set.
+ * @see DocumentsProvider#querySearchDocuments(String, String, String[])
+ * @see #getDocumentId(Uri)
+ * @see #getSearchDocumentsQuery(Uri)
+ */
+ public static Uri buildSearchDocumentsUri(
+ String authority, String parentDocumentId, String query) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority)
+ .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_SEARCH)
+ .appendQueryParameter(PARAM_QUERY, query).build();
+ }
+
+ /**
+ * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri.
+ */
+ public static String getRootId(Uri rootUri) {
+ final List<String> paths = rootUri.getPathSegments();
+ if (paths.size() < 2) {
+ throw new IllegalArgumentException("Not a root: " + rootUri);
+ }
+ if (!PATH_ROOT.equals(paths.get(0))) {
+ throw new IllegalArgumentException("Not a root: " + rootUri);
}
+ return paths.get(1);
+ }
- public static final Creator<DocumentRoot> CREATOR = new Creator<DocumentRoot>() {
- @Override
- public DocumentRoot createFromParcel(Parcel in) {
- return new DocumentRoot(in);
- }
+ /**
+ * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri.
+ */
+ public static String getDocumentId(Uri documentUri) {
+ final List<String> paths = documentUri.getPathSegments();
+ if (paths.size() < 2) {
+ throw new IllegalArgumentException("Not a document: " + documentUri);
+ }
+ if (!PATH_DOCUMENT.equals(paths.get(0))) {
+ throw new IllegalArgumentException("Not a document: " + documentUri);
+ }
+ return paths.get(1);
+ }
- @Override
- public DocumentRoot[] newArray(int size) {
- return new DocumentRoot[size];
- }
- };
+ /**
+ * Extract the search query from a Uri built by
+ * {@link #buildSearchDocumentsUri(String, String, String)}.
+ */
+ public static String getSearchDocumentsQuery(Uri searchDocumentsUri) {
+ return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
}
/**
@@ -497,6 +586,7 @@ public final class DocumentsContract {
* {@link Intent#ACTION_CREATE_DOCUMENT}.
*
* @see Context#grantUriPermission(String, Uri, int)
+ * @see Context#revokeUriPermission(Uri, int)
* @see ContentResolver#getIncomingUriPermissionGrants(int, int)
*/
public static Uri[] getOpenDocuments(Context context) {
@@ -520,20 +610,28 @@ public final class DocumentsContract {
}
/**
- * Return thumbnail representing the document at the given URI. Callers are
- * responsible for their own in-memory caching. Given document must have
- * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set.
+ * Return thumbnail representing the document at the given Uri. Callers are
+ * responsible for their own in-memory caching.
*
+ * @param documentUri document to return thumbnail for, which must have
+ * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set.
+ * @param size optimal thumbnail size desired. A provider may return a
+ * thumbnail of a different size, but never more than double the
+ * requested size.
+ * @param signal signal used to indicate that caller is no longer interested
+ * in the thumbnail.
* @return decoded thumbnail, or {@code null} if problem was encountered.
- * @hide
+ * @see DocumentsProvider#openDocumentThumbnail(String, Point,
+ * android.os.CancellationSignal)
*/
- public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) {
+ public static Bitmap getDocumentThumbnail(
+ ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) {
final Bundle openOpts = new Bundle();
openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
AssetFileDescriptor afd = null;
try {
- afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts);
+ afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal);
final FileDescriptor fd = afd.getFileDescriptor();
final long offset = afd.getStartOffset();
@@ -583,38 +681,26 @@ public final class DocumentsContract {
}
}
- /** {@hide} */
- public static List<DocumentRoot> getDocumentRoots(ContentProviderClient client) {
- try {
- final Bundle out = client.call(METHOD_GET_ROOTS, null, null);
- final List<DocumentRoot> roots = out.getParcelableArrayList(EXTRA_ROOTS);
- return roots;
- } catch (Exception e) {
- Log.w(TAG, "Failed to get roots", e);
- return null;
- }
- }
-
/**
- * Create a new document under the given parent document with MIME type and
- * display name.
+ * Create a new document with given MIME type and display name.
*
- * @param docId document with {@link Documents#FLAG_SUPPORTS_CREATE}
+ * @param parentDocumentUri directory with
+ * {@link Document#FLAG_DIR_SUPPORTS_CREATE}
* @param mimeType MIME type of new document
* @param displayName name of new document
* @return newly created document, or {@code null} if failed
- * @hide
*/
- public static String createDocument(
- ContentProviderClient client, String docId, String mimeType, String displayName) {
+ public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri,
+ String mimeType, String displayName) {
final Bundle in = new Bundle();
- in.putString(DocumentColumns.DOC_ID, docId);
- in.putString(DocumentColumns.MIME_TYPE, mimeType);
- in.putString(DocumentColumns.DISPLAY_NAME, displayName);
+ in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
+ in.putString(Document.COLUMN_MIME_TYPE, mimeType);
+ in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
try {
- final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
- return out.getString(DocumentColumns.DOC_ID);
+ final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in);
+ return buildDocumentUri(
+ parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
} catch (Exception e) {
Log.w(TAG, "Failed to create document", e);
return null;
@@ -622,40 +708,16 @@ public final class DocumentsContract {
}
/**
- * Rename the given document.
- *
- * @param docId document with {@link Documents#FLAG_SUPPORTS_RENAME}
- * @return document which may have changed due to rename, or {@code null} if
- * rename failed.
- * @hide
- */
- public static String renameDocument(
- ContentProviderClient client, String docId, String displayName) {
- final Bundle in = new Bundle();
- in.putString(DocumentColumns.DOC_ID, docId);
- in.putString(DocumentColumns.DISPLAY_NAME, displayName);
-
- try {
- final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in);
- return out.getString(DocumentColumns.DOC_ID);
- } catch (Exception e) {
- Log.w(TAG, "Failed to rename document", e);
- return null;
- }
- }
-
- /**
* Delete the given document.
*
- * @param docId document with {@link Documents#FLAG_SUPPORTS_DELETE}
- * @hide
+ * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
*/
- public static boolean deleteDocument(ContentProviderClient client, String docId) {
+ public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
final Bundle in = new Bundle();
- in.putString(DocumentColumns.DOC_ID, docId);
+ in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
try {
- client.call(METHOD_DELETE_DOCUMENT, null, in);
+ final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in);
return true;
} catch (Exception e) {
Log.w(TAG, "Failed to delete document", e);
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index eeb8c41..09f4866 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -16,16 +16,12 @@
package android.provider;
-import static android.provider.DocumentsContract.ACTION_DOCUMENT_ROOT_CHANGED;
-import static android.provider.DocumentsContract.EXTRA_AUTHORITY;
-import static android.provider.DocumentsContract.EXTRA_ROOTS;
import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE;
import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
-import static android.provider.DocumentsContract.METHOD_GET_ROOTS;
-import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
-import static android.provider.DocumentsContract.getDocId;
-import static android.provider.DocumentsContract.getSearchQuery;
+import static android.provider.DocumentsContract.getDocumentId;
+import static android.provider.DocumentsContract.getRootId;
+import static android.provider.DocumentsContract.getSearchDocumentsQuery;
import android.content.ContentProvider;
import android.content.ContentValues;
@@ -41,15 +37,12 @@ import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.OnCloseListener;
-import android.provider.DocumentsContract.DocumentColumns;
-import android.provider.DocumentsContract.DocumentRoot;
-import android.provider.DocumentsContract.Documents;
+import android.provider.DocumentsContract.Document;
import android.util.Log;
import libcore.io.IoUtils;
import java.io.FileNotFoundException;
-import java.util.List;
/**
* Base class for a document provider. A document provider should extend this
@@ -58,13 +51,13 @@ import java.util.List;
* Each document provider expresses one or more "roots" which each serve as the
* top-level of a tree. For example, a root could represent an account, or a
* physical storage device. Under each root, documents are referenced by
- * {@link DocumentColumns#DOC_ID}, which must not change once returned.
+ * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned.
* <p>
* Documents can be either an openable file (with a specific MIME type), or a
* directory containing additional documents (with the
- * {@link Documents#MIME_TYPE_DIR} MIME type). Each document can have different
- * capabilities, as described by {@link DocumentColumns#FLAGS}. The same
- * {@link DocumentColumns#DOC_ID} can be included in multiple directories.
+ * {@link Document#MIME_TYPE_DIR} MIME type). Each document can have different
+ * capabilities, as described by {@link Document#COLUMN_FLAGS}. The same
+ * {@link Document#COLUMN_DOCUMENT_ID} can be included in multiple directories.
* <p>
* Document providers must be protected with the
* {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can
@@ -78,22 +71,29 @@ import java.util.List;
public abstract class DocumentsProvider extends ContentProvider {
private static final String TAG = "DocumentsProvider";
- private static final int MATCH_DOCUMENT = 1;
- private static final int MATCH_CHILDREN = 2;
- private static final int MATCH_SEARCH = 3;
+ private static final int MATCH_ROOT = 1;
+ private static final int MATCH_RECENT = 2;
+ private static final int MATCH_DOCUMENT = 3;
+ private static final int MATCH_CHILDREN = 4;
+ private static final int MATCH_SEARCH = 5;
private String mAuthority;
private UriMatcher mMatcher;
+ /**
+ * Implementation is provided by the parent class.
+ */
@Override
public void attachInfo(Context context, ProviderInfo info) {
mAuthority = info.authority;
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- mMatcher.addURI(mAuthority, "docs/*", MATCH_DOCUMENT);
- mMatcher.addURI(mAuthority, "docs/*/children", MATCH_CHILDREN);
- mMatcher.addURI(mAuthority, "docs/*/search", MATCH_SEARCH);
+ mMatcher.addURI(mAuthority, "root", MATCH_ROOT);
+ mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
+ mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
+ mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
+ mMatcher.addURI(mAuthority, "document/*/search", MATCH_SEARCH);
// Sanity check our setup
if (!info.exported) {
@@ -111,83 +111,80 @@ public abstract class DocumentsProvider extends ContentProvider {
}
/**
- * Return list of all document roots provided by this document provider.
- * When this list changes, a provider must call
- * {@link #notifyDocumentRootsChanged()}.
- */
- public abstract List<DocumentRoot> getDocumentRoots();
-
- /**
- * Create and return a new document. A provider must allocate a new
- * {@link DocumentColumns#DOC_ID} to represent the document, which must not
- * change once returned.
+ * Create a new document and return its {@link Document#COLUMN_DOCUMENT_ID}.
+ * A provider must allocate a new {@link Document#COLUMN_DOCUMENT_ID} to
+ * represent the document, which must not change once returned.
*
- * @param docId the parent directory to create the new document under.
+ * @param documentId the parent directory to create the new document under.
* @param mimeType the MIME type associated with the new document.
* @param displayName the display name of the new document.
*/
@SuppressWarnings("unused")
- public String createDocument(String docId, String mimeType, String displayName)
+ public String createDocument(String documentId, String mimeType, String displayName)
throws FileNotFoundException {
throw new UnsupportedOperationException("Create not supported");
}
/**
- * Rename the given document.
+ * Delete the given document. Upon returning, any Uri permission grants for
+ * the given document will be revoked. If additional documents were deleted
+ * as a side effect of this call, such as documents inside a directory, the
+ * implementor is responsible for revoking those permissions.
*
- * @param docId the document to rename.
- * @param displayName the new display name.
+ * @param documentId the document to delete.
*/
@SuppressWarnings("unused")
- public void renameDocument(String docId, String displayName) throws FileNotFoundException {
- throw new UnsupportedOperationException("Rename not supported");
+ public void deleteDocument(String documentId) throws FileNotFoundException {
+ throw new UnsupportedOperationException("Delete not supported");
}
- /**
- * Delete the given document.
- *
- * @param docId the document to delete.
- */
+ public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
+
@SuppressWarnings("unused")
- public void deleteDocument(String docId) throws FileNotFoundException {
- throw new UnsupportedOperationException("Delete not supported");
+ public Cursor queryRecentDocuments(String rootId, String[] projection)
+ throws FileNotFoundException {
+ throw new UnsupportedOperationException("Recent not supported");
}
/**
* Return metadata for the given document. A provider should avoid making
* network requests to keep this request fast.
*
- * @param docId the document to return.
+ * @param documentId the document to return.
*/
- public abstract Cursor queryDocument(String docId) throws FileNotFoundException;
+ public abstract Cursor queryDocument(String documentId, String[] projection)
+ throws FileNotFoundException;
/**
* Return the children of the given document which is a directory.
*
- * @param docId the directory to return children for.
+ * @param parentDocumentId the directory to return children for.
*/
- public abstract Cursor queryDocumentChildren(String docId) throws FileNotFoundException;
+ public abstract Cursor queryChildDocuments(
+ String parentDocumentId, String[] projection, String sortOrder)
+ throws FileNotFoundException;
/**
* Return documents that that match the given query, starting the search at
* the given directory.
*
- * @param docId the directory to start search at.
+ * @param parentDocumentId the directory to start search at.
*/
@SuppressWarnings("unused")
- public Cursor querySearch(String docId, String query) throws FileNotFoundException {
+ public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection)
+ throws FileNotFoundException {
throw new UnsupportedOperationException("Search not supported");
}
/**
* Return MIME type for the given document. Must match the value of
- * {@link DocumentColumns#MIME_TYPE} for this document.
+ * {@link Document#COLUMN_MIME_TYPE} for this document.
*/
- public String getType(String docId) throws FileNotFoundException {
- final Cursor cursor = queryDocument(docId);
+ public String getDocumentType(String documentId) throws FileNotFoundException {
+ final Cursor cursor = queryDocument(documentId, null);
try {
if (cursor.moveToFirst()) {
- return cursor.getString(cursor.getColumnIndexOrThrow(DocumentColumns.MIME_TYPE));
+ return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
} else {
return null;
}
@@ -233,7 +230,7 @@ public abstract class DocumentsProvider extends ContentProvider {
* @param sizeHint hint of the optimal thumbnail dimensions.
* @param signal used by the caller to signal if the request should be
* cancelled.
- * @see Documents#FLAG_SUPPORTS_THUMBNAIL
+ * @see Document#FLAG_SUPPORTS_THUMBNAIL
*/
@SuppressWarnings("unused")
public AssetFileDescriptor openDocumentThumbnail(
@@ -241,17 +238,31 @@ public abstract class DocumentsProvider extends ContentProvider {
throw new UnsupportedOperationException("Thumbnails not supported");
}
+ /**
+ * Implementation is provided by the parent class. Cannot be overriden.
+ *
+ * @see #queryRoots(String[])
+ * @see #queryRecentDocuments(String, String[])
+ * @see #queryDocument(String, String[])
+ * @see #queryChildDocuments(String, String[], String)
+ * @see #querySearchDocuments(String, String, String[])
+ */
@Override
- public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
+ public final Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
try {
switch (mMatcher.match(uri)) {
+ case MATCH_ROOT:
+ return queryRoots(projection);
+ case MATCH_RECENT:
+ return queryRecentDocuments(getRootId(uri), projection);
case MATCH_DOCUMENT:
- return queryDocument(getDocId(uri));
+ return queryDocument(getDocumentId(uri), projection);
case MATCH_CHILDREN:
- return queryDocumentChildren(getDocId(uri));
+ return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
case MATCH_SEARCH:
- return querySearch(getDocId(uri), getSearchQuery(uri));
+ return querySearchDocuments(
+ getDocumentId(uri), getSearchDocumentsQuery(uri), projection);
default:
throw new UnsupportedOperationException("Unsupported Uri " + uri);
}
@@ -261,12 +272,17 @@ public abstract class DocumentsProvider extends ContentProvider {
}
}
+ /**
+ * Implementation is provided by the parent class. Cannot be overriden.
+ *
+ * @see #getDocumentType(String)
+ */
@Override
public final String getType(Uri uri) {
try {
switch (mMatcher.match(uri)) {
case MATCH_DOCUMENT:
- return getType(getDocId(uri));
+ return getDocumentType(getDocumentId(uri));
default:
return null;
}
@@ -276,22 +292,39 @@ public abstract class DocumentsProvider extends ContentProvider {
}
}
+ /**
+ * Implementation is provided by the parent class. Throws by default, and
+ * cannot be overriden.
+ *
+ * @see #createDocument(String, String, String)
+ */
@Override
public final Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException("Insert not supported");
}
+ /**
+ * Implementation is provided by the parent class. Throws by default, and
+ * cannot be overriden.
+ *
+ * @see #deleteDocument(String)
+ */
@Override
public final int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Delete not supported");
}
+ /**
+ * Implementation is provided by the parent class. Throws by default, and
+ * cannot be overriden.
+ */
@Override
public final int update(
Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Update not supported");
}
+ /** {@hide} */
@Override
public final Bundle callFromPackage(
String callingPackage, String method, String arg, Bundle extras) {
@@ -300,33 +333,25 @@ public abstract class DocumentsProvider extends ContentProvider {
return super.callFromPackage(callingPackage, method, arg, extras);
}
- // Platform operations require the caller explicitly hold manage
- // permission; Uri permissions don't extend management operations.
- getContext().enforceCallingOrSelfPermission(
- android.Manifest.permission.MANAGE_DOCUMENTS, "Document management");
+ // Require that caller can manage given document
+ final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID);
+ final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
+ getContext().enforceCallingOrSelfUriPermission(
+ documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method);
final Bundle out = new Bundle();
try {
- if (METHOD_GET_ROOTS.equals(method)) {
- final List<DocumentRoot> roots = getDocumentRoots();
- out.putParcelableList(EXTRA_ROOTS, roots);
-
- } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
- final String docId = extras.getString(DocumentColumns.DOC_ID);
- final String mimeType = extras.getString(DocumentColumns.MIME_TYPE);
- final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME);
+ if (METHOD_CREATE_DOCUMENT.equals(method)) {
+ final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
+ final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
- // TODO: issue Uri grant towards caller
- final String newDocId = createDocument(docId, mimeType, displayName);
- out.putString(DocumentColumns.DOC_ID, newDocId);
-
- } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
- final String docId = extras.getString(DocumentColumns.DOC_ID);
- final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME);
- renameDocument(docId, displayName);
+ // TODO: issue Uri grant towards calling package
+ // TODO: enforce that package belongs to caller
+ final String newDocumentId = createDocument(documentId, mimeType, displayName);
+ out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId);
} else if (METHOD_DELETE_DOCUMENT.equals(method)) {
- final String docId = extras.getString(DocumentColumns.DOC_ID);
+ final String docId = extras.getString(Document.COLUMN_DOCUMENT_ID);
deleteDocument(docId);
} else {
@@ -338,47 +363,57 @@ public abstract class DocumentsProvider extends ContentProvider {
return out;
}
+ /**
+ * Implementation is provided by the parent class.
+ *
+ * @see #openDocument(String, String, CancellationSignal)
+ */
@Override
public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
- return openDocument(getDocId(uri), mode, null);
+ return openDocument(getDocumentId(uri), mode, null);
}
+ /**
+ * Implementation is provided by the parent class.
+ *
+ * @see #openDocument(String, String, CancellationSignal)
+ */
@Override
public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
throws FileNotFoundException {
- return openDocument(getDocId(uri), mode, signal);
+ return openDocument(getDocumentId(uri), mode, signal);
}
+ /**
+ * Implementation is provided by the parent class.
+ *
+ * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+ */
@Override
public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
throws FileNotFoundException {
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
- return openDocumentThumbnail(getDocId(uri), sizeHint, null);
+ return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
} else {
return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
}
}
+ /**
+ * Implementation is provided by the parent class.
+ *
+ * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+ */
@Override
public final AssetFileDescriptor openTypedAssetFile(
Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
throws FileNotFoundException {
if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
- return openDocumentThumbnail(getDocId(uri), sizeHint, signal);
+ return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
} else {
return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
}
}
-
- /**
- * Notify system that {@link #getDocumentRoots()} has changed, usually due to an
- * account or device change.
- */
- public void notifyDocumentRootsChanged() {
- final Intent intent = new Intent(ACTION_DOCUMENT_ROOT_CHANGED);
- intent.putExtra(EXTRA_AUTHORITY, mAuthority);
- getContext().sendBroadcast(intent);
- }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 802bedf..83e1544 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -690,6 +690,19 @@ public final class Settings {
public static final String ACTION_NOTIFICATION_LISTENER_SETTINGS
= "android.settings.NOTIFICATION_LISTENER_SETTINGS";
+ /**
+ * Activity Action: Show settings for video captioning.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard
+ * against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
+
// End of Intent actions for Settings
/**
@@ -3588,7 +3601,7 @@ public final class Settings {
* <li>{@link #ACCESSIBILITY_CAPTIONING_EDGE_COLOR}
* <li>{@link #ACCESSIBILITY_CAPTIONING_EDGE_TYPE}
* <li>{@link #ACCESSIBILITY_CAPTIONING_TYPEFACE}
- * <li>{@link #ACCESSIBILITY_CAPTIONING_FONT_SIZE}
+ * <li>{@link #ACCESSIBILITY_CAPTIONING_FONT_SCALE}
* </ul>
*
* @hide
@@ -3610,9 +3623,8 @@ public final class Settings {
* Integer property that specifies the preset style for captions, one
* of:
* <ul>
- * <li>{@link android.view.accessibility.CaptioningManager#PRESET_WHITE_ON_BLACK}
- * <li>{@link android.view.accessibility.CaptioningManager#PRESET_BLACK_ON_WHITE}
- * <li>{@link android.view.accessibility.CaptioningManager#PRESET_CUSTOM}
+ * <li>{@link android.view.accessibility.CaptioningManager.CaptionStyle#PRESET_CUSTOM}
+ * <li>a valid index of {@link android.view.accessibility.CaptioningManager.CaptionStyle#PRESETS}
* </ul>
*
* @see java.util.Locale#toString
@@ -3644,9 +3656,9 @@ public final class Settings {
/**
* Integer property that specifes the edge type for captions, one of:
* <ul>
- * <li>{@link android.view.accessibility.CaptioningManager#EDGE_TYPE_NONE}
- * <li>{@link android.view.accessibility.CaptioningManager#EDGE_TYPE_OUTLINE}
- * <li>{@link android.view.accessibility.CaptioningManager#EDGE_TYPE_DROP_SHADOWED}
+ * <li>{@link android.view.accessibility.CaptioningManager.CaptionStyle#EDGE_TYPE_NONE}
+ * <li>{@link android.view.accessibility.CaptioningManager.CaptionStyle#EDGE_TYPE_OUTLINE}
+ * <li>{@link android.view.accessibility.CaptioningManager.CaptionStyle#EDGE_TYPE_DROP_SHADOW}
* </ul>
*
* @see #ACCESSIBILITY_CAPTIONING_EDGE_COLOR
@@ -3682,13 +3694,12 @@ public final class Settings {
"accessibility_captioning_typeface";
/**
- * Integer point property that specifies font size for captions in
- * scaled pixels (sp).
+ * Floating point property that specifies font scaling for captions.
*
* @hide
*/
- public static final String ACCESSIBILITY_CAPTIONING_FONT_SIZE =
- "accessibility_captioning_font_size";
+ public static final String ACCESSIBILITY_CAPTIONING_FONT_SCALE =
+ "accessibility_captioning_font_scale";
/**
* The timout for considering a press to be a long press in milliseconds.
@@ -3697,13 +3708,22 @@ public final class Settings {
public static final String LONG_PRESS_TIMEOUT = "long_press_timeout";
/**
- * List of the enabled print providers.
+ * List of the enabled print services.
* @hide
*/
public static final String ENABLED_PRINT_SERVICES =
"enabled_print_services";
/**
+ * List of the system print services we enabled on first boot. On
+ * first boot we enable all system, i.e. bundled print services,
+ * once, so they work out-of-the-box.
+ * @hide
+ */
+ public static final String ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES =
+ "enabled_on_first_boot_system_print_services";
+
+ /**
* Setting to always use the default text-to-speech settings regardless
* of the application settings.
* 1 = override application settings,
@@ -4327,7 +4347,7 @@ public final class Settings {
ACCESSIBILITY_CAPTIONING_EDGE_TYPE,
ACCESSIBILITY_CAPTIONING_EDGE_COLOR,
ACCESSIBILITY_CAPTIONING_TYPEFACE,
- ACCESSIBILITY_CAPTIONING_FONT_SIZE,
+ ACCESSIBILITY_CAPTIONING_FONT_SCALE,
TTS_USE_DEFAULTS,
TTS_DEFAULT_RATE,
TTS_DEFAULT_PITCH,
diff --git a/core/java/android/security/IKeystoreService.java b/core/java/android/security/IKeystoreService.java
index 3d75dc8..bf8d4e5 100644
--- a/core/java/android/security/IKeystoreService.java
+++ b/core/java/android/security/IKeystoreService.java
@@ -244,7 +244,8 @@ public interface IKeystoreService extends IInterface {
return _result;
}
- public int generate(String name, int uid, int flags) throws RemoteException {
+ public int generate(String name, int uid, int keyType, int keySize, int flags,
+ byte[][] args) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
int _result;
@@ -252,7 +253,17 @@ public interface IKeystoreService extends IInterface {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(name);
_data.writeInt(uid);
+ _data.writeInt(keyType);
+ _data.writeInt(keySize);
_data.writeInt(flags);
+ if (args == null) {
+ _data.writeInt(0);
+ } else {
+ _data.writeInt(args.length);
+ for (int i = 0; i < args.length; i++) {
+ _data.writeByteArray(args[i]);
+ }
+ }
mRemote.transact(Stub.TRANSACTION_generate, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
@@ -560,7 +571,8 @@ public interface IKeystoreService extends IInterface {
public int zero() throws RemoteException;
- public int generate(String name, int uid, int flags) throws RemoteException;
+ public int generate(String name, int uid, int keyType, int keySize, int flags, byte[][] args)
+ throws RemoteException;
public int import_key(String name, byte[] data, int uid, int flags) throws RemoteException;
diff --git a/core/java/android/speech/tts/SynthesisRequest.java b/core/java/android/speech/tts/SynthesisRequest.java
index 917a109..12a026b 100644
--- a/core/java/android/speech/tts/SynthesisRequest.java
+++ b/core/java/android/speech/tts/SynthesisRequest.java
@@ -30,7 +30,7 @@ import android.os.Bundle;
* </ul>
*
* Any additional parameters sent to the text to speech service are passed in
- * uninterpreted, see the @code{params} argument in {@link TextToSpeech#speak}
+ * uninterpreted, see the {@code params} argument in {@link TextToSpeech#speak}
* and {@link TextToSpeech#synthesizeToFile}.
*/
public final class SynthesisRequest {
diff --git a/core/java/android/util/LayoutDirection.java b/core/java/android/util/LayoutDirection.java
index e37d2f2..20af20b 100644
--- a/core/java/android/util/LayoutDirection.java
+++ b/core/java/android/util/LayoutDirection.java
@@ -17,11 +17,15 @@
package android.util;
/**
- * An interface for defining layout directions. A layout direction can be left-to-right (LTR)
+ * A class for defining layout directions. A layout direction can be left-to-right (LTR)
* or right-to-left (RTL). It can also be inherited (from a parent) or deduced from the default
* language script of a locale.
*/
-public interface LayoutDirection {
+public final class LayoutDirection {
+
+ // No instantiation
+ private LayoutDirection() {}
+
/**
* Horizontal layout direction is from Left to Right.
*/
diff --git a/core/java/android/view/accessibility/CaptioningManager.java b/core/java/android/view/accessibility/CaptioningManager.java
index b1be24c..d4c6abe 100644
--- a/core/java/android/view/accessibility/CaptioningManager.java
+++ b/core/java/android/view/accessibility/CaptioningManager.java
@@ -17,58 +17,77 @@
package android.view.accessibility;
import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
import android.graphics.Color;
import android.graphics.Typeface;
+import android.net.Uri;
+import android.os.Handler;
import android.provider.Settings.Secure;
import android.text.TextUtils;
+import java.util.ArrayList;
import java.util.Locale;
/**
- * Contains methods for accessing preferred video captioning state and
+ * Contains methods for accessing and monitoring preferred video captioning state and visual
* properties.
+ * <p>
+ * To obtain a handle to the captioning manager, do the following:
+ * <p>
+ * <code>
+ * <pre>CaptioningManager captioningManager =
+ * (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);</pre>
+ * </code>
*/
public class CaptioningManager {
- /**
- * Activity Action: Show settings for video captioning.
- * <p>
- * In some cases, a matching Activity may not exist, so ensure you safeguard
- * against this.
- * <p>
- * Input: Nothing.
- * <p>
- * Output: Nothing.
- */
- public static final String ACTION_CAPTIONING_SETTINGS = "android.settings.CAPTIONING_SETTINGS";
+ /** Default captioning enabled value. */
+ private static final int DEFAULT_ENABLED = 0;
+ /** Default style preset as an index into {@link CaptionStyle#PRESETS}. */
private static final int DEFAULT_PRESET = 0;
- private static final int DEFAULT_ENABLED = 0;
- private static final float DEFAULT_FONT_SIZE = 24;
+
+ /** Default scaling value for caption fonts. */
+ private static final float DEFAULT_FONT_SCALE = 1;
+
+ private final ArrayList<CaptioningChangeListener>
+ mListeners = new ArrayList<CaptioningChangeListener>();
+ private final Handler mHandler = new Handler();
+
+ private final ContentResolver mContentResolver;
+
+ /**
+ * Creates a new captioning manager for the specified context.
+ *
+ * @hide
+ */
+ public CaptioningManager(Context context) {
+ mContentResolver = context.getContentResolver();
+ }
/**
- * @param cr Resolver to access the database with.
- * @return The user's preferred caption enabled state.
+ * @return the user's preferred captioning enabled state
*/
- public static final boolean isEnabled(ContentResolver cr) {
- return Secure.getInt(cr, Secure.ACCESSIBILITY_CAPTIONING_ENABLED, DEFAULT_ENABLED) == 1;
+ public final boolean isEnabled() {
+ return Secure.getInt(
+ mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_ENABLED, DEFAULT_ENABLED) == 1;
}
/**
- * @param cr Resolver to access the database with.
- * @return The raw locale string for the user's preferred caption language.
+ * @return the raw locale string for the user's preferred captioning
+ * language
* @hide
*/
- public static final String getRawLocale(ContentResolver cr) {
- return Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
+ public final String getRawLocale() {
+ return Secure.getString(mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
}
/**
- * @param cr Resolver to access the database with.
- * @return The locale for the user's preferred caption language, or null if
- * not specified.
+ * @return the locale for the user's preferred captioning language, or null
+ * if not specified
*/
- public static final Locale getLocale(ContentResolver cr) {
- final String rawLocale = getRawLocale(cr);
+ public final Locale getLocale() {
+ final String rawLocale = getRawLocale();
if (!TextUtils.isEmpty(rawLocale)) {
final String[] splitLocale = rawLocale.split("_");
switch (splitLocale.length) {
@@ -85,14 +104,151 @@ public class CaptioningManager {
}
/**
- * @param cr Resolver to access the database with.
- * @return The user's preferred font size for video captions, or 0 if not
- * specified.
+ * @return the user's preferred font scaling factor for video captions, or 1 if not
+ * specified
*/
- public static final float getFontSize(ContentResolver cr) {
- return Secure.getFloat(cr, Secure.ACCESSIBILITY_CAPTIONING_FONT_SIZE, DEFAULT_FONT_SIZE);
+ public final float getFontScale() {
+ return Secure.getFloat(
+ mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, DEFAULT_FONT_SCALE);
+ }
+
+ /**
+ * @return the raw preset number, or the first preset if not specified
+ * @hide
+ */
+ public int getRawUserStyle() {
+ return Secure.getInt(
+ mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_PRESET, DEFAULT_PRESET);
+ }
+
+ /**
+ * @return the user's preferred visual properties for captions as a
+ * {@link CaptionStyle}, or the default style if not specified
+ */
+ public CaptionStyle getUserStyle() {
+ final int preset = getRawUserStyle();
+ if (preset == CaptionStyle.PRESET_CUSTOM) {
+ return CaptionStyle.getCustomStyle(mContentResolver);
+ }
+
+ return CaptionStyle.PRESETS[preset];
+ }
+
+ /**
+ * Adds a listener for changes in the user's preferred captioning enabled
+ * state and visual properties.
+ *
+ * @param listener the listener to add
+ */
+ public void addCaptioningStateChangeListener(CaptioningChangeListener listener) {
+ synchronized (mListeners) {
+ if (mListeners.isEmpty()) {
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_ENABLED);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE);
+ registerObserver(Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
+ }
+
+ mListeners.add(listener);
+ }
+ }
+
+ private void registerObserver(String key) {
+ mContentResolver.registerContentObserver(Secure.getUriFor(key), false, mContentObserver);
+ }
+
+ /**
+ * Removes a listener previously added using
+ * {@link #addCaptioningStateChangeListener}.
+ *
+ * @param listener the listener to remove
+ */
+ public void removeCaptioningStateChangeListener(CaptioningChangeListener listener) {
+ synchronized (mListeners) {
+ mListeners.remove(listener);
+
+ if (mListeners.isEmpty()) {
+ mContentResolver.unregisterContentObserver(mContentObserver);
+ }
+ }
+ }
+
+ private void notifyEnabledChanged() {
+ final boolean enabled = isEnabled();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onEnabledChanged(enabled);
+ }
+ }
+ }
+
+ private void notifyUserStyleChanged() {
+ final CaptionStyle userStyle = getUserStyle();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onUserStyleChanged(userStyle);
+ }
+ }
}
+ private void notifyLocaleChanged() {
+ final Locale locale = getLocale();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onLocaleChanged(locale);
+ }
+ }
+ }
+
+ private void notifyFontScaleChanged() {
+ final float fontScale = getFontScale();
+ synchronized (mListeners) {
+ for (CaptioningChangeListener listener : mListeners) {
+ listener.onFontScaleChanged(fontScale);
+ }
+ }
+ }
+
+ private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ final String uriPath = uri.getPath();
+ final String name = uriPath.substring(uriPath.lastIndexOf('/') + 1);
+ if (Secure.ACCESSIBILITY_CAPTIONING_ENABLED.equals(name)) {
+ notifyEnabledChanged();
+ } else if (Secure.ACCESSIBILITY_CAPTIONING_LOCALE.equals(name)) {
+ notifyLocaleChanged();
+ } else if (Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE.equals(name)) {
+ notifyFontScaleChanged();
+ } else {
+ // We only need a single callback when multiple style properties
+ // change in rapid succession.
+ mHandler.removeCallbacks(mStyleChangedRunnable);
+ mHandler.post(mStyleChangedRunnable);
+ }
+ }
+ };
+
+ /**
+ * Runnable posted when user style properties change. This is used to
+ * prevent unnecessary change notifications when multiple properties change
+ * in rapid succession.
+ */
+ private final Runnable mStyleChangedRunnable = new Runnable() {
+ @Override
+ public void run() {
+ notifyUserStyleChanged();
+ }
+ };
+
+ /**
+ * Specifies visual properties for video captions, including foreground and
+ * background colors, edge properties, and typeface.
+ */
public static final class CaptionStyle {
private static final CaptionStyle WHITE_ON_BLACK;
private static final CaptionStyle BLACK_ON_WHITE;
@@ -155,8 +311,8 @@ public class CaptioningManager {
}
/**
- * @return The preferred {@link Typeface} for video captions, or null if
- * not specified.
+ * @return the preferred {@link Typeface} for video captions, or null if
+ * not specified
*/
public Typeface getTypeface() {
if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) {
@@ -168,41 +324,20 @@ public class CaptioningManager {
/**
* @hide
*/
- public static int getRawPreset(ContentResolver cr) {
- return Secure.getInt(cr, Secure.ACCESSIBILITY_CAPTIONING_PRESET, DEFAULT_PRESET);
- }
-
- /**
- * @param cr Resolver to access the database with.
- * @return The user's preferred caption style.
- */
- public static CaptionStyle defaultUserStyle(ContentResolver cr) {
- final int preset = getRawPreset(cr);
- if (preset == PRESET_CUSTOM) {
- return getCustomStyle(cr);
- }
-
- return PRESETS[preset];
- }
-
- /**
- * @hide
- */
public static CaptionStyle getCustomStyle(ContentResolver cr) {
+ final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM;
final int foregroundColor = Secure.getInt(
- cr, Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR,
- DEFAULT_CUSTOM.foregroundColor);
- final int backgroundColor = Secure.getInt(cr,
- Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR,
- DEFAULT_CUSTOM.backgroundColor);
+ cr, Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, defStyle.foregroundColor);
+ final int backgroundColor = Secure.getInt(
+ cr, Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, defStyle.backgroundColor);
final int edgeType = Secure.getInt(
- cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, DEFAULT_CUSTOM.edgeType);
+ cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, defStyle.edgeType);
final int edgeColor = Secure.getInt(
- cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, DEFAULT_CUSTOM.edgeColor);
+ cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor);
String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
if (rawTypeface == null) {
- rawTypeface = DEFAULT_CUSTOM.mRawTypeface;
+ rawTypeface = defStyle.mRawTypeface;
}
return new CaptionStyle(
@@ -226,4 +361,45 @@ public class CaptioningManager {
DEFAULT_CUSTOM = WHITE_ON_BLACK;
}
}
+
+ /**
+ * Listener for changes in captioning properties, including enabled state
+ * and user style preferences.
+ */
+ public abstract class CaptioningChangeListener {
+ /**
+ * Called when the captioning enabled state changes.
+ *
+ * @param enabled the user's new preferred captioning enabled state
+ */
+ public void onEnabledChanged(boolean enabled) {
+ }
+
+ /**
+ * Called when the captioning user style changes.
+ *
+ * @param userStyle the user's new preferred style
+ * @see CaptioningManager#getUserStyle()
+ */
+ public void onUserStyleChanged(CaptionStyle userStyle) {
+ }
+
+ /**
+ * Called when the captioning locale changes.
+ *
+ * @param locale the preferred captioning locale
+ * @see CaptioningManager#getLocale()
+ */
+ public void onLocaleChanged(Locale locale) {
+ }
+
+ /**
+ * Called when the captioning font scaling factor changes.
+ *
+ * @param fontScale the preferred font scaling factor
+ * @see CaptioningManager#getFontScale()
+ */
+ public void onFontScaleChanged(float fontScale) {
+ }
+ }
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index f97e3dd..54b87de 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1876,9 +1876,13 @@ public final class InputMethodManager {
}
/**
- * Returns true if the current IME needs to offer the users a way to switch to a next input
- * method. When the user triggers it, the IME has to call {@link #switchToNextInputMethod} to
- * switch to a next input method which is selected by the system.
+ * Returns true if the current IME needs to offer the users ways to switch to a next input
+ * method (e.g. a globe key.).
+ * When an IME sets supportsSwitchingToNextInputMethod and this method returns true,
+ * the IME has to offer ways to to invoke {@link #switchToNextInputMethod} accordingly.
+ * <p> Note that the system determines the most appropriate next input method
+ * and subtype in order to provide the consistent user experience in switching
+ * between IMEs and subtypes.
* @param imeToken Supplies the identifying token given to an input method when it was started,
* which allows it to perform this operation on itself.
*/
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 3923539..eded438 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -31,7 +31,9 @@ import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Looper;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.StrictMode;
+import android.print.PrintAttributes;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
@@ -49,7 +51,6 @@ import android.widget.AbsoluteLayout;
import java.io.BufferedWriter;
import java.io.File;
-import java.io.OutputStream;
import java.util.Map;
/**
@@ -1042,7 +1043,9 @@ public class WebView extends AbsoluteLayout
* Exports the contents of this Webview as PDF. Only supported for API levels
* {@link android.os.Build.VERSION_CODES#KEY_LIME_PIE} and above.
*
- * @param out The stream to export the PDF contents to. Cannot be null.
+ * TODO(sgurun) the parameter list is stale. Fix it before unhiding.
+ *
+ * @param fd The FileDescriptor to export the PDF contents to. Cannot be null.
* @param width The page width. Should be larger than 0.
* @param height The page height. Should be larger than 0.
* @param resultCallback A callback to be invoked when the PDF content is exported.
@@ -1051,21 +1054,26 @@ public class WebView extends AbsoluteLayout
* be null.
*
* The PDF conversion is done asynchronously and the PDF output is written to the provided
- * outputstream. The caller should not close the outputstream until the resultCallback is
- * called, indicating PDF conversion is complete. Webview cannot be drawn during the pdf
- * export so the application is recommended to take it offscreen, or putting in a layer
- * with an overlaid progress UI / spinner.
+ * file descriptor. The caller should not close the file descriptor until the resultCallback
+ * is called, indicating PDF conversion is complete. Webview will never close the file
+ * descriptor.
+ * Limitations: Webview cannot be drawn during the PDF export so the application is
+ * recommended to take it offscreen, or putting in a layer with an overlaid progress
+ * UI / spinner.
*
* If the caller cancels the task using the cancellationSignal, the cancellation will be
* acked using the resultCallback signal.
*
+ * Throws an exception if an IO error occurs accessing the file descriptor.
+ *
* TODO(sgurun) margins, explain the units, make it public.
* @hide
*/
- public void exportToPdf(OutputStream out, int width, int height,
- ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal) {
+ public void exportToPdf(ParcelFileDescriptor fd, PrintAttributes attributes,
+ ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal)
+ throws java.io.IOException {
checkThread();
- mProvider.exportToPdf(out, width, height, resultCallback, cancellationSignal);
+ mProvider.exportToPdf(fd, attributes, resultCallback, cancellationSignal);
}
/**
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index db98d30..b1a7878 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -62,6 +62,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.print.PrintAttributes;
import android.security.KeyChain;
import android.text.Editable;
import android.text.InputType;
@@ -2896,11 +2897,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
* See {@link WebView#exportToPdf()}
*/
@Override
- public void exportToPdf(java.io.OutputStream out, int width, int height,
- ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal) {
+ public void exportToPdf(android.os.ParcelFileDescriptor fd, PrintAttributes attributes,
+ ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal)
+ throws java.io.IOException {
// K-only API not implemented in WebViewClassic.
throw new IllegalStateException("This API not supported on Android 4.3 and earlier");
-
}
/**
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 8fe6edf..d625d8a 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -27,6 +27,8 @@ import android.net.http.SslCertificate;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.print.PrintAttributes;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
@@ -41,7 +43,6 @@ import android.webkit.WebView.PictureListener;
import java.io.BufferedWriter;
import java.io.File;
-import java.io.OutputStream;
import java.util.Map;
/**
@@ -148,8 +149,9 @@ public interface WebViewProvider {
public Picture capturePicture();
- public void exportToPdf(OutputStream out, int width, int height,
- ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal);
+ public void exportToPdf(ParcelFileDescriptor fd, PrintAttributes attributes,
+ ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal)
+ throws java.io.IOException;
public float getScale();
diff --git a/core/java/com/android/internal/widget/AutoScrollHelper.java b/core/java/com/android/internal/widget/AutoScrollHelper.java
index 6298e35..afa4103 100644
--- a/core/java/com/android/internal/widget/AutoScrollHelper.java
+++ b/core/java/com/android/internal/widget/AutoScrollHelper.java
@@ -32,7 +32,8 @@ import android.widget.AbsListView;
* scrolling to Views.
* <p>
* <b>Note:</b> Implementing classes are responsible for overriding the
- * {@link #onScrollBy} method to scroll the target view. See
+ * {@link #scrollTargetBy}, {@link #canTargetScrollHorizontally}, and
+ * {@link #canTargetScrollVertically} methods. See
* {@link AbsListViewAutoScroller} for an {@link android.widget.AbsListView}
* -specific implementation.
* <p>
@@ -60,12 +61,14 @@ import android.widget.AbsListView;
* {@link #setMaximumEdges}. Default value is {@link #NO_MAX}.
* </ul>
* <h1>Scrolling</h1> When automatic scrolling is active, the helper will
- * repeatedly call {@link #onScrollBy} to apply new scrolling offsets.
+ * repeatedly call {@link #scrollTargetBy} to apply new scrolling offsets.
* <p>
* The following scrolling properties may be configured:
* <ul>
* <li>Acceleration ramp-up duration, see {@link #setRampUpDuration}. Default
- * value is 2.5 seconds.
+ * value is 2500 milliseconds.
+ * <li>Acceleration ramp-down duration, see {@link #setRampDownDuration}.
+ * Default value is 500 milliseconds.
* <li>Target velocity relative to view size, see {@link #setRelativeVelocity}.
* Default value is 100% per second for both vertical and horizontal.
* <li>Minimum velocity used to constrain relative velocity, see
@@ -163,25 +166,22 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
private float[] mMaximumVelocity = new float[] { NO_MAX, NO_MAX };
/** Whether to start activation immediately. */
- private boolean mSkipDelay;
+ private boolean mAlreadyDelayed;
/** Whether to reset the scroller start time on the next animation. */
- private boolean mResetScroller;
+ private boolean mNeedsReset;
- /** Whether the auto-scroller is active. */
- private boolean mActive;
+ /** Whether to send a cancel motion event to the target view. */
+ private boolean mNeedsCancel;
- /** Whether the auto-scroller is scrolling. */
- private boolean mScrolling;
+ /** Whether the auto-scroller is actively scrolling. */
+ private boolean mAnimating;
/** Whether the auto-scroller is enabled. */
private boolean mEnabled;
/** Whether the auto-scroller consumes events when scrolling. */
- private boolean mExclusiveEnabled;
-
- /** Down time of the most recent down touch event. */
- private long mDownTime;
+ private boolean mExclusive;
// Default values.
private static final int DEFAULT_EDGE_TYPE = EDGE_TYPE_INSIDE_EXTEND;
@@ -192,7 +192,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
private static final float DEFAULT_RELATIVE_VELOCITY = 1f;
private static final int DEFAULT_ACTIVATION_DELAY = ViewConfiguration.getTapTimeout();
private static final int DEFAULT_RAMP_UP_DURATION = 2500;
- // TODO: RAMP_DOWN_DURATION of 500ms?
+ private static final int DEFAULT_RAMP_DOWN_DURATION = 500;
/**
* Creates a new helper for scrolling the specified target view.
@@ -220,8 +220,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
setRelativeVelocity(DEFAULT_RELATIVE_VELOCITY, DEFAULT_RELATIVE_VELOCITY);
setActivationDelay(DEFAULT_ACTIVATION_DELAY);
setRampUpDuration(DEFAULT_RAMP_UP_DURATION);
-
- mEnabled = true;
+ setRampDownDuration(DEFAULT_RAMP_DOWN_DURATION);
}
/**
@@ -232,8 +231,8 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* @return The scroll helper, which may used to chain setter calls.
*/
public AutoScrollHelper setEnabled(boolean enabled) {
- if (!enabled) {
- stop(true);
+ if (mEnabled && !enabled) {
+ requestStop();
}
mEnabled = enabled;
@@ -255,13 +254,13 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* When enabled, {@link #onTouch} will return true if the helper is
* currently scrolling and false otherwise.
*
- * @param enabled True to exclusively handle touch events during scrolling,
+ * @param exclusive True to exclusively handle touch events during scrolling,
* false to allow the target view to receive all touch events.
- * @see #isExclusiveEnabled()
- * @see #onTouch(View, MotionEvent)
+ * @return The scroll helper, which may used to chain setter calls.
*/
- public void setExclusiveEnabled(boolean enabled) {
- mExclusiveEnabled = enabled;
+ public AutoScrollHelper setExclusive(boolean exclusive) {
+ mExclusive = exclusive;
+ return this;
}
/**
@@ -270,10 +269,10 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
*
* @return True if exclusive handling of touch events during scrolling is
* enabled, false otherwise.
- * @see #setExclusiveEnabled(boolean)
+ * @see #setExclusive(boolean)
*/
- public boolean isExclusiveEnabled() {
- return mExclusiveEnabled;
+ public boolean isExclusive() {
+ return mExclusive;
}
/**
@@ -424,7 +423,22 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* @return The scroll helper, which may used to chain setter calls.
*/
public AutoScrollHelper setRampUpDuration(int durationMillis) {
- mScroller.setDuration(durationMillis);
+ mScroller.setRampUpDuration(durationMillis);
+ return this;
+ }
+
+ /**
+ * Sets the amount of time after de-activation of auto-scrolling that is
+ * takes to slow to a stop.
+ * <p>
+ * Specifying a duration greater than zero prevents sudden jumps in
+ * velocity.
+ *
+ * @param durationMillis The ramp-down duration in milliseconds.
+ * @return The scroll helper, which may used to chain setter calls.
+ */
+ public AutoScrollHelper setRampDownDuration(int durationMillis) {
+ mScroller.setRampDownDuration(durationMillis);
return this;
}
@@ -432,7 +446,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* Handles touch events by activating automatic scrolling, adjusting scroll
* velocity, or stopping.
* <p>
- * If {@link #isExclusiveEnabled()} is false, always returns false so that
+ * If {@link #isExclusive()} is false, always returns false so that
* the host view may handle touch events. Otherwise, returns true when
* automatic scrolling is active and false otherwise.
*/
@@ -445,52 +459,135 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN:
- mDownTime = event.getDownTime();
+ mNeedsCancel = true;
+ mAlreadyDelayed = false;
+ // $FALL-THROUGH$
case MotionEvent.ACTION_MOVE:
- final float xValue = getEdgeValue(mRelativeEdges[HORIZONTAL], v.getWidth(),
- mMaximumEdges[HORIZONTAL], event.getX());
- final float yValue = getEdgeValue(mRelativeEdges[VERTICAL], v.getHeight(),
- mMaximumEdges[VERTICAL], event.getY());
- final float maxVelX = constrain(mRelativeVelocity[HORIZONTAL] * mTarget.getWidth(),
- mMinimumVelocity[HORIZONTAL], mMaximumVelocity[HORIZONTAL]);
- final float maxVelY = constrain(mRelativeVelocity[VERTICAL] * mTarget.getHeight(),
- mMinimumVelocity[VERTICAL], mMaximumVelocity[VERTICAL]);
- mScroller.setTargetVelocity(xValue * maxVelX, yValue * maxVelY);
-
- if ((xValue != 0 || yValue != 0) && !mActive) {
- mActive = true;
- mResetScroller = true;
- if (mRunnable == null) {
- mRunnable = new AutoScrollRunnable();
- }
- if (mSkipDelay) {
- mTarget.postOnAnimation(mRunnable);
- } else {
- mSkipDelay = true;
- mTarget.postOnAnimationDelayed(mRunnable, mActivationDelay);
- }
+ final float xTargetVelocity = computeTargetVelocity(
+ HORIZONTAL, event.getX(), v.getWidth(), mTarget.getWidth());
+ final float yTargetVelocity = computeTargetVelocity(
+ VERTICAL, event.getY(), v.getHeight(), mTarget.getHeight());
+ mScroller.setTargetVelocity(xTargetVelocity, yTargetVelocity);
+
+ // If the auto scroller was not previously active, but it should
+ // be, then update the state and start animations.
+ if (!mAnimating && shouldAnimate()) {
+ startAnimating();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
- stop(true);
+ requestStop();
break;
}
- return mExclusiveEnabled && mScrolling;
+ return mExclusive && mAnimating;
}
/**
- * Override this method to scroll the target view by the specified number
- * of pixels.
- * <p>
- * Returns whether the target view was able to scroll the requested amount.
+ * @return whether the target is able to scroll in the requested direction
+ */
+ private boolean shouldAnimate() {
+ final ClampedScroller scroller = mScroller;
+ final int verticalDirection = scroller.getVerticalDirection();
+ final int horizontalDirection = scroller.getHorizontalDirection();
+
+ return verticalDirection != 0 && canTargetScrollVertically(verticalDirection)
+ || horizontalDirection != 0 && canTargetScrollHorizontally(horizontalDirection);
+ }
+
+ /**
+ * Starts the scroll animation.
+ */
+ private void startAnimating() {
+ if (mRunnable == null) {
+ mRunnable = new ScrollAnimationRunnable();
+ }
+
+ mAnimating = true;
+ mNeedsReset = true;
+
+ if (!mAlreadyDelayed && mActivationDelay > 0) {
+ mTarget.postOnAnimationDelayed(mRunnable, mActivationDelay);
+ } else {
+ mRunnable.run();
+ }
+
+ // If we start animating again before the user lifts their finger, we
+ // already know it's not a tap and don't need an activation delay.
+ mAlreadyDelayed = true;
+ }
+
+ /**
+ * Requests that the scroll animation slow to a stop. If there is an
+ * activation delay, this may occur between posting the animation and
+ * actually running it.
+ */
+ private void requestStop() {
+ if (mNeedsReset) {
+ // The animation has been posted, but hasn't run yet. Manually
+ // stopping animation will prevent it from running.
+ mAnimating = false;
+ } else {
+ mScroller.requestStop();
+ }
+ }
+
+ private float computeTargetVelocity(
+ int direction, float coordinate, float srcSize, float dstSize) {
+ final float relativeEdge = mRelativeEdges[direction];
+ final float maximumEdge = mMaximumEdges[direction];
+ final float value = getEdgeValue(relativeEdge, srcSize, maximumEdge, coordinate);
+ if (value == 0) {
+ // The edge in this direction is not activated.
+ return 0;
+ }
+
+ final float relativeVelocity = mRelativeVelocity[direction];
+ final float minimumVelocity = mMinimumVelocity[direction];
+ final float maximumVelocity = mMaximumVelocity[direction];
+ final float targetVelocity = relativeVelocity * dstSize;
+
+ // Target velocity is adjusted for interpolated edge position, then
+ // clamped to the minimum and maximum values. Later, this value will be
+ // adjusted for time-based acceleration.
+ if (value > 0) {
+ return constrain(value * targetVelocity, minimumVelocity, maximumVelocity);
+ } else {
+ return -constrain(-value * targetVelocity, minimumVelocity, maximumVelocity);
+ }
+ }
+
+ /**
+ * Override this method to scroll the target view by the specified number of
+ * pixels.
+ *
+ * @param deltaX The number of pixels to scroll by horizontally.
+ * @param deltaY The number of pixels to scroll by vertically.
+ */
+ public abstract void scrollTargetBy(int deltaX, int deltaY);
+
+ /**
+ * Override this method to return whether the target view can be scrolled
+ * horizontally in a certain direction.
+ *
+ * @param direction Negative to check scrolling left, positive to check
+ * scrolling right.
+ * @return true if the target view is able to horizontally scroll in the
+ * specified direction.
+ */
+ public abstract boolean canTargetScrollHorizontally(int direction);
+
+ /**
+ * Override this method to return whether the target view can be scrolled
+ * vertically in a certain direction.
*
- * @param deltaX The amount to scroll in the X direction, in pixels.
- * @param deltaY The amount to scroll in the Y direction, in pixels.
- * @return true if the target view was able to scroll the requested amount.
+ * @param direction Negative to check scrolling up, positive to check
+ * scrolling down.
+ * @return true if the target view is able to vertically scroll in the
+ * specified direction.
*/
- public abstract boolean onScrollBy(int deltaX, int deltaY);
+ public abstract boolean canTargetScrollVertically(int direction);
/**
* Returns the interpolated position of a touch point relative to an edge
@@ -534,7 +631,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
if (current >= 0) {
// Movement up to the edge is scaled.
return 1f - current / leading;
- } else if (mActive && (mEdgeType == EDGE_TYPE_INSIDE_EXTEND)) {
+ } else if (mAnimating && (mEdgeType == EDGE_TYPE_INSIDE_EXTEND)) {
// Movement beyond the edge is always maximum.
return 1f;
}
@@ -551,7 +648,7 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
return 0;
}
- private static float constrain(float value, float min, float max) {
+ private static int constrain(int value, int min, int max) {
if (value > max) {
return max;
} else if (value < min) {
@@ -561,19 +658,13 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
}
}
- /**
- * Stops auto-scrolling immediately, optionally reseting the auto-scrolling
- * delay.
- *
- * @param reset Whether to reset the auto-scrolling delay.
- */
- private void stop(boolean reset) {
- mActive = false;
- mScrolling = false;
- mSkipDelay = !reset;
-
- if (mRunnable != null) {
- mTarget.removeCallbacks(mRunnable);
+ private static float constrain(float value, float min, float max) {
+ if (value > max) {
+ return max;
+ } else if (value < min) {
+ return min;
+ } else {
+ return value;
}
}
@@ -582,52 +673,44 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* canceling any ongoing touch events.
*/
private void cancelTargetTouch() {
+ final long eventTime = SystemClock.uptimeMillis();
final MotionEvent cancel = MotionEvent.obtain(
- mDownTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0);
- cancel.setAction(MotionEvent.ACTION_CANCEL);
+ eventTime, eventTime, MotionEvent.ACTION_CANCEL, 0, 0, 0);
mTarget.onTouchEvent(cancel);
cancel.recycle();
}
- private class AutoScrollRunnable implements Runnable {
+ private class ScrollAnimationRunnable implements Runnable {
@Override
public void run() {
- if (!mActive) {
+ if (!mAnimating) {
return;
}
- if (mResetScroller) {
- mResetScroller = false;
+ if (mNeedsReset) {
+ mNeedsReset = false;
mScroller.start();
}
- final View target = mTarget;
final ClampedScroller scroller = mScroller;
+ if (scroller.isFinished() || !shouldAnimate()) {
+ mAnimating = false;
+ return;
+ }
+
+ if (mNeedsCancel) {
+ mNeedsCancel = false;
+ cancelTargetTouch();
+ }
+
scroller.computeScrollDelta();
final int deltaX = scroller.getDeltaX();
final int deltaY = scroller.getDeltaY();
- if ((deltaX != 0 || deltaY != 0 || !scroller.isFinished())
- && onScrollBy(deltaX, deltaY)) {
- // Update whether we're actively scrolling.
- final boolean scrolling = (deltaX != 0 || deltaY != 0);
- if (mScrolling != scrolling) {
- mScrolling = scrolling;
-
- // If we just started actively scrolling, make sure any down
- // or move events send to the target view are canceled.
- if (mExclusiveEnabled && scrolling) {
- cancelTargetTouch();
- }
- }
+ scrollTargetBy(deltaX, deltaY);
- // Keep going until the scroller has permanently stopped or the
- // view can't scroll any more. If the user moves their finger
- // again, we'll repost the animation.
- target.postOnAnimation(this);
- } else {
- stop(false);
- }
+ // Keep going until the scroller has permanently stopped.
+ mTarget.postOnAnimation(this);
}
}
@@ -637,27 +720,39 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* interpolated 1f value after a specified duration.
*/
private static class ClampedScroller {
- private final Interpolator mInterpolator = new AccelerateInterpolator();
-
- private int mDuration;
+ private int mRampUpDuration;
+ private int mRampDownDuration;
private float mTargetVelocityX;
private float mTargetVelocityY;
private long mStartTime;
+
private long mDeltaTime;
private int mDeltaX;
private int mDeltaY;
+ private long mStopTime;
+ private float mStopValue;
+ private int mEffectiveRampDown;
+
/**
* Creates a new ramp-up scroller that reaches full velocity after a
* specified duration.
*/
public ClampedScroller() {
- reset();
+ mStartTime = Long.MIN_VALUE;
+ mStopTime = -1;
+ mDeltaTime = 0;
+ mDeltaX = 0;
+ mDeltaY = 0;
+ }
+
+ public void setRampUpDuration(int durationMillis) {
+ mRampUpDuration = durationMillis;
}
- public void setDuration(int durationMillis) {
- mDuration = durationMillis;
+ public void setRampDownDuration(int durationMillis) {
+ mRampDownDuration = durationMillis;
}
/**
@@ -665,32 +760,50 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
*/
public void start() {
mStartTime = AnimationUtils.currentAnimationTimeMillis();
+ mStopTime = -1;
mDeltaTime = mStartTime;
+ mStopValue = 0.5f;
+ mDeltaX = 0;
+ mDeltaY = 0;
}
/**
- * Returns whether the scroller is finished, which means that its
- * acceleration is zero.
- *
- * @return Whether the scroller is finished.
+ * Stops the scroller at the current animation time.
*/
+ public void requestStop() {
+ final long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ mEffectiveRampDown = constrain((int) (currentTime - mStartTime), 0, mRampDownDuration);
+ mStopValue = getValueAt(currentTime);
+ mStopTime = currentTime;
+ }
+
public boolean isFinished() {
- if (mTargetVelocityX == 0 && mTargetVelocityY == 0) {
- return true;
+ return mStopTime > 0
+ && AnimationUtils.currentAnimationTimeMillis() > mStopTime + mEffectiveRampDown;
+ }
+
+ private float getValueAt(long currentTime) {
+ if (currentTime < mStartTime) {
+ return 0f;
+ } else if (mStopTime < 0 || currentTime < mStopTime) {
+ final long elapsedSinceStart = currentTime - mStartTime;
+ return 0.5f * constrain(elapsedSinceStart / (float) mRampUpDuration, 0, 1);
+ } else {
+ final long elapsedSinceEnd = currentTime - mStopTime;
+ return (1 - mStopValue) + mStopValue
+ * constrain(elapsedSinceEnd / (float) mEffectiveRampDown, 0, 1);
}
- final long currentTime = AnimationUtils.currentAnimationTimeMillis();
- final long elapsedSinceStart = currentTime - mStartTime;
- return elapsedSinceStart > mDuration;
}
/**
- * Stops the scroller and resets its values.
+ * Interpolates the value along a parabolic curve corresponding to the equation
+ * <code>y = -4x * (x-1)</code>.
+ *
+ * @param value The value to interpolate, between 0 and 1.
+ * @return the interpolated value, between 0 and 1.
*/
- public void reset() {
- mStartTime = -1;
- mDeltaTime = -1;
- mDeltaX = 0;
- mDeltaY = 0;
+ private float interpolateValue(float value) {
+ return -4 * value * value + 4 * value;
}
/**
@@ -701,18 +814,13 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
* @see #getDeltaY()
*/
public void computeScrollDelta() {
- final long currentTime = AnimationUtils.currentAnimationTimeMillis();
- final long elapsedSinceStart = currentTime - mStartTime;
- final float value;
- if (mStartTime < 0) {
- value = 0f;
- } else if (elapsedSinceStart < mDuration) {
- value = (float) elapsedSinceStart / mDuration;
- } else {
- value = 1f;
+ if (mDeltaTime == 0) {
+ throw new RuntimeException("Cannot compute scroll delta before calling start()");
}
- final float scale = mInterpolator.getInterpolation(value);
+ final long currentTime = AnimationUtils.currentAnimationTimeMillis();
+ final float value = getValueAt(currentTime);
+ final float scale = interpolateValue(value);
final long elapsedSinceDelta = currentTime - mDeltaTime;
mDeltaTime = currentTime;
@@ -731,6 +839,14 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
mTargetVelocityY = y;
}
+ public int getHorizontalDirection() {
+ return (int) (mTargetVelocityX / Math.abs(mTargetVelocityX));
+ }
+
+ public int getVerticalDirection() {
+ return (int) (mTargetVelocityY / Math.abs(mTargetVelocityY));
+ }
+
/**
* The distance traveled in the X-coordinate computed by the last call
* to {@link #computeScrollDelta()}.
@@ -749,20 +865,60 @@ public abstract class AutoScrollHelper implements View.OnTouchListener {
}
/**
- * Implementation of {@link AutoScrollHelper} that knows how to scroll
- * generic {@link AbsListView}s.
+ * An implementation of {@link AutoScrollHelper} that knows how to scroll
+ * through an {@link AbsListView}.
*/
public static class AbsListViewAutoScroller extends AutoScrollHelper {
private final AbsListView mTarget;
public AbsListViewAutoScroller(AbsListView target) {
super(target);
+
mTarget = target;
}
@Override
- public boolean onScrollBy(int deltaX, int deltaY) {
- return mTarget.scrollListBy(deltaY);
+ public void scrollTargetBy(int deltaX, int deltaY) {
+ mTarget.scrollListBy(deltaY);
+ }
+
+ @Override
+ public boolean canTargetScrollHorizontally(int direction) {
+ // List do not scroll horizontally.
+ return false;
+ }
+
+ @Override
+ public boolean canTargetScrollVertically(int direction) {
+ final AbsListView target = mTarget;
+ final int itemCount = target.getCount();
+ final int childCount = target.getChildCount();
+ final int firstPosition = target.getFirstVisiblePosition();
+ final int lastPosition = firstPosition + childCount;
+
+ if (direction > 0) {
+ // Are we already showing the entire last item?
+ if (lastPosition >= itemCount) {
+ final View lastView = target.getChildAt(childCount - 1);
+ if (lastView.getBottom() <= target.getHeight()) {
+ return false;
+ }
+ }
+ } else if (direction < 0) {
+ // Are we already showing the entire first item?
+ if (firstPosition <= 0) {
+ final View firstView = target.getChildAt(0);
+ if (firstView.getTop() >= 0) {
+ return false;
+ }
+ }
+ } else {
+ // The behavior for direction 0 is undefined and we can return
+ // whatever we want.
+ return false;
+ }
+
+ return true;
}
}
}
diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
index a3ce2a5..50b3302 100644
--- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp
+++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp
@@ -351,9 +351,9 @@ not_valid_surface:
"Make sure the SurfaceTexture is valid");
return 0;
}
-
+
sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(_env, native_window));
- window = new Surface(producer);
+ window = new Surface(producer, true);
if (window == NULL)
goto not_valid_surface;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 9613df3..3ee2657 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1095,6 +1095,12 @@
android:description="@string/permdesc_use_sip"
android:label="@string/permlab_use_sip" />
+ <!-- Allows an application to request CallHandlerService implementations. -->
+ <permission android:name="android.permission.CAN_REQUEST_HANDLE_CALL_SERVICE"
+ android:permissionGroup="android.permission-group.PHONE_CALLS"
+ android:protectionLevel="system" />
+
+
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
<!-- ================================== -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5444cb1..f8cffa7 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2379,7 +2379,13 @@
method should be considered an option as the default. -->
<attr name="isDefault" format="boolean" />
<!-- Set to true if this input method supports ways to switch to
- a next input method (e.g. a globe key.). -->
+ a next input method (e.g. a globe key.). When this is true and
+ InputMethodManager#shouldOfferSwitchingToNextInputMethod() returns true,
+ the IME has to offer ways to to invoke InputMethodManager#switchToNextInputMethod()
+ accordingly.
+ <p> Note that the system determines the most appropriate next input method
+ and subtype in order to provide the consistent user experience in switching
+ between IMEs and subtypes. -->
<attr name="supportsSwitchingToNextInputMethod" format="boolean" />
</declare-styleable>
diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk
index 22fa7fc..be55444 100644
--- a/core/tests/coretests/Android.mk
+++ b/core/tests/coretests/Android.mk
@@ -22,7 +22,7 @@ LOCAL_SRC_FILES := \
$(call all-java-files-under, EnabledTestApp/src)
LOCAL_DX_FLAGS := --core-library
-LOCAL_STATIC_JAVA_LIBRARIES := core-tests android-common frameworks-core-util-lib mockwebserver guava littlemock
+LOCAL_STATIC_JAVA_LIBRARIES := core-tests-support android-common frameworks-core-util-lib mockwebserver guava littlemock
LOCAL_JAVA_LIBRARIES := android.test.runner conscrypt telephony-common
LOCAL_PACKAGE_NAME := FrameworksCoreTests
diff --git a/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java
deleted file mode 100644
index 417a85f..0000000
--- a/core/tests/coretests/src/android/webkit/AccessibilityInjectorTest.java
+++ /dev/null
@@ -1,1809 +0,0 @@
-/*
- * Copyright (C) 2010 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.webkit;
-
-import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.view.KeyEvent;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-
-/**
- * This is a test for the behavior of the {@link AccessibilityInjector}
- * which is used by {@link WebView} to provide basic accessibility support
- * in case JavaScript is disabled.
- * </p>
- * Note: This test works against the generated {@link AccessibilityEvent}s
- * to so it also checks if the test for announcing navigation axis and
- * status messages as appropriate.
- */
-public class AccessibilityInjectorTest
- extends ActivityInstrumentationTestCase2<AccessibilityInjectorTestActivity> {
-
- /** The timeout to wait for the expected selection. */
- private static final long TIMEOUT_WAIT_FOR_SELECTION_STRING = 1000;
-
- /** The timeout to wait for accessibility and the mock service to be enabled. */
- private static final long TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE = 1000;
-
- /** The count of tests to detect when to shut down the service. */
- private static final int TEST_CASE_COUNT = 19;
-
- /** The meta state for pressed left ALT. */
- private static final int META_STATE_ALT_LEFT_ON = KeyEvent.META_ALT_ON
- | KeyEvent.META_ALT_LEFT_ON;
-
- /** Prefix for the CSS style span appended by WebKit. */
- private static final String APPLE_SPAN_PREFIX = "<span class=\"Apple-style-span\"";
-
- /** Suffix for the CSS style span appended by WebKit. */
- private static final String APPLE_SPAN_SUFFIX = "</span>";
-
- /** The value for not specified selection string since null is a valid value. */
- private static final String SELECTION_STRING_UNKNOWN = "Unknown";
-
- /** Lock for locking the test. */
- private static final Object sTestLock = new Object();
-
- /** Key bindings used for testing. */
- private static final String TEST_KEY_DINDINGS =
- "0x13=0x01000100;" +
- "0x14=0x01010100;" +
- "0x15=0x04000000;" +
- "0x16=0x04000000;" +
- "0x200000013=0x03020701:0x03010201:0x03000101:0x03030001:0x03040001:0x03050001:0x03060001;" +
- "0x200000014=0x03010001:0x03020101:0x03070201:0x03030701:0x03040701:0x03050701:0x03060701;" +
- "0x200000015=0x03040301:0x03050401:0x03060501:0x03000601:0x03010601:0x03020601:0x03070601;" +
- "0x200000016=0x03050601:0x03040501:0x03030401:0x03020301:0x03070301:0x03010301:0x03000301;";
-
- /** Handle to the test for use by the mock service. */
- private static AccessibilityInjectorTest sInstance;
-
- /** Flag indicating if the accessibility service is ready to receive events. */
- private static boolean sIsAccessibilityServiceReady;
-
- /** The count of executed tests to detect when to toggle accessibility and the service. */
- private static int sExecutedTestCount;
-
- /** Worker thread with a handler to perform non test thread processing. */
- private Worker mWorker;
-
- /** Handle to the {@link WebView} to load data in. */
- private WebView mWebView;
-
- /** Used for caching the default bindings so they can be restored. */
- private static String sDefaultKeyBindings;
-
- /** The received selection string for assertion checking. */
- private static String sReceivedSelectionString = SELECTION_STRING_UNKNOWN;
-
- public AccessibilityInjectorTest() {
- super(AccessibilityInjectorTestActivity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mWorker = new Worker();
- sInstance = this;
- if (sExecutedTestCount == 0) {
- // until JUnit4 comes to play with @BeforeTest
- disableAccessibilityAndMockAccessibilityService();
- enableAccessibilityAndMockAccessibilityService();
- injectTestWebContentKeyBindings();
- }
- }
-
- @Override
- protected void tearDown() throws Exception {
- if (mWorker != null) {
- mWorker.stop();
- }
- if (sExecutedTestCount == TEST_CASE_COUNT) {
- // until JUnit4 comes to play with @AfterTest
- disableAccessibilityAndMockAccessibilityService();
- restoreDefaultWebContentKeyBindings();
- }
- super.tearDown();
- }
-
- /**
- * Tests navigation by character.
- */
- @LargeTest
- public void testNavigationByCharacter() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<p>" +
- "a<b>b</b>c" +
- "</p>" +
- "<p>" +
- "d" +
- "<p/>" +
- "e" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // change navigation axis to word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
- assertSelectionString("1"); // expect the word navigation axis
-
- // change navigation axis to character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
- assertSelectionString("0"); // expect the character navigation axis
-
- // go to the first character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("a");
-
- // go to the second character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<b>b</b>");
-
- // go to the third character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("c");
-
- // go to the fourth character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("d");
-
- // go to the fifth character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("e");
-
- // try to go past the last character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the fifth character (reverse)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("e");
-
- // go to the fourth character (reverse)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("d");
-
- // go to the third character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("c");
-
- // go to the second character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<b>b</b>");
-
- // go to the first character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("a");
-
- // try to go before the first character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("a");
-
- // go to the second character (reverse again)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<b>b</b>");
- }
-
- /**
- * Tests navigation by word.
- */
- @LargeTest
- public void testNavigationByWord() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<p>" +
- "This is <b>a</b> sentence" +
- "</p>" +
- "<p>" +
- " scattered " +
- "<p/>" +
- " all over " +
- "</p>" +
- "<div>" +
- "<p>the place.</p>" +
- "</div>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // change navigation axis to word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
- assertSelectionString("1"); // expect the word navigation axis
-
- // go to the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("This");
-
- // go to the second word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("is");
-
- // go to the third word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<b>a</b>");
-
- // go to the fourth word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("sentence");
-
- // go to the fifth word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("scattered");
-
- // go to the sixth word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("all");
-
- // go to the seventh word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("over");
-
- // go to the eight word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("the");
-
- // go to the ninth word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("place");
-
- // NOTE: WebKit selection returns the dot as a word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(".");
-
- // try to go past the last word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the last word (reverse)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("place.");
-
- // go to the eight word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("the");
-
- // go to the seventh word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("over");
-
- // go to the sixth word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("all");
-
- // go to the fifth word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("scattered");
-
- // go to the fourth word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("sentence");
-
- // go to the third word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<b>a</b>");
-
- // go to the second word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("is");
-
- // go to the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("This");
-
- // try to go before the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("This");
-
- // go to the second word (reverse again)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("is");
- }
-
- /**
- * Tests navigation by sentence.
- */
- @LargeTest
- public void testNavigationBySentence() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<div>" +
- "<p>" +
- "This is the first sentence of the first paragraph and has an <b>inline bold tag</b>." +
- "This is the second sentence of the first paragraph." +
- "</p>" +
- "<h1>This is a heading</h1>" +
- "<p>" +
- "This is the first sentence of the second paragraph." +
- "This is the second sentence of the second paragraph." +
- "</p>" +
- "</div>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // Sentence axis is the default
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("This is the first sentence of the first paragraph and has an "
- + "<b>inline bold tag</b>.");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("This is the second sentence of the first paragraph.");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("This is a heading");
-
- // go to the fourth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("This is the first sentence of the second paragraph.");
-
- // go to the fifth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("This is the second sentence of the second paragraph.");
-
- // try to go past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the fifth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("This is the second sentence of the second paragraph.");
-
- // go to the fourth sentence (reverse)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("This is the first sentence of the second paragraph.");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("This is a heading");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("This is the second sentence of the first paragraph.");
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("This is the first sentence of the first paragraph and has an "
- + "<b>inline bold tag</b>.");
-
- // try to go before the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("This is the first sentence of the first paragraph and has an "
- + "<b>inline bold tag</b>.");
-
- // go to the second sentence (reverse again)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("This is the second sentence of the first paragraph.");
- }
-
- /**
- * Tests navigation by heading.
- */
- @LargeTest
- public void testNavigationByHeading() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<h1>Heading one</h1>" +
- "<p>" +
- "This is some text" +
- "</p>" +
- "<h2>Heading two</h2>" +
- "<p>" +
- "This is some text" +
- "</p>" +
- "<h3>Heading three</h3>" +
- "<p>" +
- "This is some text" +
- "</p>" +
- "<h4>Heading four</h4>" +
- "<p>" +
- "This is some text" +
- "</p>" +
- "<h5>Heading five</h5>" +
- "<p>" +
- "This is some text" +
- "</p>" +
- "<h6>Heading six</h6>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // change navigation axis to heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
- assertSelectionString("3"); // expect the heading navigation axis
-
- // go to the first heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<h1>Heading one</h1>");
-
- // go to the second heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<h2>Heading two</h2>");
-
- // go to the third heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<h3>Heading three</h3>");
-
- // go to the fourth heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<h4>Heading four</h4>");
-
- // go to the fifth heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<h5>Heading five</h5>");
-
- // go to the sixth heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<h6>Heading six</h6>");
-
- // try to go past the last heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the fifth heading (reverse)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<h5>Heading five</h5>");
-
- // go to the fourth heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<h4>Heading four</h4>");
-
- // go to the third heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<h3>Heading three</h3>");
-
- // go to the second heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<h2>Heading two</h2>");
-
- // go to the first heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<h1>Heading one</h1>");
-
- // try to go before the first heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the second heading (reverse again)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<h2>Heading two</h2>");
- }
-
- /**
- * Tests navigation by sibling.
- */
- @LargeTest
- public void testNavigationBySibing() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<h1>Heading one</h1>" +
- "<p>" +
- "This is some text" +
- "</p>" +
- "<div>" +
- "<button>Input</button>" +
- "</div>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // change navigation axis to heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
- assertSelectionString("3"); // expect the heading navigation axis
-
- // change navigation axis to sibling
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
- assertSelectionString("4"); // expect the sibling navigation axis
-
- // change navigation axis to parent/first child
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
- assertSelectionString("5"); // expect the parent/first child navigation axis
-
- // go to the first child of the body
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<h1>Heading one</h1>");
-
- // change navigation axis to sibling
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
- assertSelectionString("4"); // expect the sibling navigation axis
-
- // go to the next sibling
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<p>This is some text</p>");
-
- // go to the next sibling
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<div><button>Input</button></div>");
-
- // try to go past the last sibling
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the previous sibling (reverse)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<p>This is some text</p>");
-
- // go to the previous sibling
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<h1>Heading one</h1>");
-
- // try to go before the previous sibling
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the next sibling (reverse again)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<p>This is some text</p>");
- }
-
- /**
- * Tests navigation by parent/first child.
- */
- @LargeTest
- public void testNavigationByParentFirstChild() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<div>" +
- "<button>Input</button>" +
- "</div>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // change navigation axis to document
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
- assertSelectionString("6"); // expect the document navigation axis
-
- // change navigation axis to parent/first child
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
- assertSelectionString("5"); // expect the parent/first child navigation axis
-
- // go to the first child
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<div><button>Input</button></div>");
-
- // go to the first child
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<button>Input</button>");
-
- // try to go to the first child of a leaf element
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the parent (reverse)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<div><button>Input</button></div>");
-
- // go to the parent
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<body><div><button>Input</button></div></body>");
-
- // try to go to the body parent
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first child (reverse again)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<div><button>Input</button></div>");
- }
-
- /**
- * Tests navigation by document.
- */
- @LargeTest
- public void testNavigationByDocument() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<button>Click</button>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // change navigation axis to document
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_LEFT, META_STATE_ALT_LEFT_ON);
- assertSelectionString("6"); // expect the document navigation axis
-
- // go to the bottom of the document
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Click");
-
- // go to the top of the document (reverse)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<body><button>Click</button></body>");
-
- // go to the bottom of the document (reverse again)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Click");
- }
-
- /**
- * Tests the sync between the text navigation and navigation by DOM elements.
- */
- @LargeTest
- public void testSyncBetweenTextAndDomNodeNavigation() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<p>" +
- "First" +
- "</p>" +
- "<button>Second</button>" +
- "<p>" +
- "Third" +
- "</p>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // change navigation axis to word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
- assertSelectionString("1"); // expect the word navigation axis
-
- // go to the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
-
- // change navigation axis to heading
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
- assertSelectionString("3"); // expect the heading navigation axis
-
- // change navigation axis to sibling
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_RIGHT, META_STATE_ALT_LEFT_ON);
- assertSelectionString("4"); // expect the sibling navigation axis
-
- // go to the next sibling
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<button>Second</button>");
-
- // change navigation axis to character
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, META_STATE_ALT_LEFT_ON);
- assertSelectionString("0"); // expect the character navigation axis
-
- // change navigation axis to word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, META_STATE_ALT_LEFT_ON);
- assertSelectionString("1"); // expect the word navigation axis
-
- // go to the next word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Third");
- }
-
- /**
- * Tests that the selection does not cross anchor boundaries. This is a
- * workaround for the asymmetric and inconsistent handling of text with
- * links by WebKit while traversing by sentence.
- */
- @LargeTest
- public void testEnforceSelectionDoesNotCrossAnchorBoundary1() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<div>First</div>" +
- "<p>" +
- "<a href=\"\">Second</a> Third" +
- "</p>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<div>First</div>");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<a href=\"\">Second</a>");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Third");
-
- // go to past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("Third");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<a href=\"\">Second</a>");
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("First");
-
- // go to before the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<div>First</div>");
- }
-
- /**
- * Tests that the selection does not cross anchor boundaries. This is a
- * workaround for the asymmetric and inconsistent handling of text with
- * links by WebKit while traversing by sentence.
- */
- @LargeTest
- public void testEnforceSelectionDoesNotCrossAnchorBoundary2() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<div>First</div>" +
- "<a href=\"#\">Second</a>" +
- "&nbsp;" +
- "<a href=\"#\">Third</a>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<a href=\"#\">Second</a>");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("&nbsp;");
-
- // go to the fourth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<a href=\"#\">Third</a>");
-
- // go to past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the fourth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<a href=\"#\">Third</a>");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("&nbsp;");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<a href=\"#\">Second</a>");
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("First");
-
- // go to before the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
- }
-
- /**
- * Tests that the selection does not cross anchor boundaries. This is a
- * workaround for the asymmetric and inconsistent handling of text with
- * links by WebKit while traversing by sentence.
- */
- @LargeTest
- public void testEnforceSelectionDoesNotCrossAnchorBoundary3() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<div>" +
- "First" +
- "<div>" +
- "<div>" +
- "<a href=\"#\">Second</a>" +
- "</div>" +
- "<div>" +
- "Third" +
- "</div>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<a href=\"#\">Second</a>");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Third");
-
- // go to past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("Third");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<a href=\"#\">Second</a>");
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("First");
-
- // go to before the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
- }
-
- /**
- * Tests skipping of content with hidden visibility.
- */
- @LargeTest
- public void testSkipVisibilityHidden() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<div>First </div>" +
- "<div style=\"visibility:hidden;\">Second</div>" +
- "<div> Third</div>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // change navigation axis to word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
- assertSelectionString("1"); // expect the word navigation axis
-
- // go to the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
-
- // go to the third word (the second is invisible)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Third");
-
- // go to past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the third word (the second is invisible)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("Third");
-
- // go to the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("First");
-
- // go to before the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
- }
-
- /**
- * Tests skipping of content with display none.
- */
- @LargeTest
- public void testSkipDisplayNone() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<div>First</div>" +
- "<div style=\"display: none;\">Second</div>" +
- "<div>Third</div>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // change navigation axis to word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, META_STATE_ALT_LEFT_ON);
- assertSelectionString("1"); // expect the word navigation axis
-
- // go to the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
-
- // go to the third word (the second is invisible)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Third");
-
- // go to past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the third word (the second is invisible)
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("Third");
-
- // go to the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("First");
-
- // go to before the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first word
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
- }
-
- /**
- * Tests for the selection not getting stuck.
- *
- * Note: The selection always proceeds but if it can
- * be selecting the same content i.e. between the start
- * and end are contained the same text nodes.
- */
- @LargeTest
- public void testSelectionTextProceed() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<a href=\"#\">First</a>" +
- "<span><a href=\"#\"><span>Second</span>&nbsp;<small>a</small></a>" +
- "</span>&nbsp;<a href=\"#\">Third</a>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<a href=\"#\">First</a>");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<a href=\"#\"><span>Second&nbsp;<small>a</small></a>");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("&nbsp;");
-
- // go to the fourth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<a href=\"#\">Third</a>");
-
- // go to past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<a href=\"#\">Third</a>");
-
- // NOTE: Here we are a bit asymmetric around whitespace but we can live with it
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("&nbsp;");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<a href=\"#\"><span>Second&nbsp;<small>a</small></a>");
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<a href=\"#\">First</a>");
-
- // go to before the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<a href=\"#\">First</a>");
- }
-
- /**
- * Tests if input elements are selected rather skipped.
- */
- @LargeTest
- public void testSelectionOfInputElements() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<p>" +
- "First" +
- "</p>" +
- "<input type=\"text\"/>" +
- "<p>" +
- "Second" +
- "</p>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<input type=\"text\">");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Second");
-
- // go to past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("Second");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<input type=\"text\">");
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("First");
-
- // go to before the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
- }
-
- /**
- * Tests traversing of input controls.
- */
- @LargeTest
- public void testSelectionOfInputElements2() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<div>" +
- "First" +
- "<input type=\"text\"/>" +
- "<span>" +
- "<input type=\"text\"/>" +
- "</span>" +
- "<button type=\"button\">Click Me!</button>" +
- "<div>" +
- "<input type=\"submit\"/>" +
- "</div>" +
- "<p>" +
- "Second" +
- "</p>" +
- "</div>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<input type=\"text\">");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<input type=\"text\">");
-
- // go to the fourth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<button type=\"button\">Click Me!</button>");
-
- // go to the fifth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<input type=\"submit\">");
-
- // go to the sixth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Second");
-
- // go to past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the sixth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("Second");
-
- // go to the fifth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<input type=\"submit\">");
-
- // go to the fourth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<button type=\"button\">Click Me!</button>");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<input type=\"text\">");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<input type=\"text\">");
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("First");
- }
-
- /**
- * Tests traversing of input controls.
- */
- @LargeTest
- public void testSelectionOfInputElements3() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<input type=\"text\"/>" +
- "<button type=\"button\">Click Me!</button>" +
- "<select>" +
- "<option value=\"volvo\">Volvo</option>" +
- "<option value=\"saab\">Saab</option>" +
- "</select>" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<input type=\"text\">");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<button type=\"button\">Click Me!</button>");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<select><option value=\"volvo\">Volvo</option>" +
- "<option value=\"saab\">Saab</option></select>");
-
- // go to past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<select><option value=\"volvo\">Volvo</option>" +
- "<option value=\"saab\">Saab</option></select>");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<button type=\"button\">Click Me!</button>");
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<input type=\"text\">");
-
- // go to before the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<input type=\"text\">");
- }
-
- /**
- * Tests traversing of input controls.
- */
- @LargeTest
- public void testSelectionOfInputElements4() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "Start" +
- "<span>" +
- "<span>" +
- "<input type=\"submit\">" +
- "</span>" +
- "</span>" +
- "<input type=\"text\" size=\"30\">" +
- "<span>" +
- "<span>" +
- "<input type=\"submit\" size=\"30\">" +
- "</span>" +
- "</span>" +
- "End" +
- "</body>" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Start");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<input type=\"submit\">");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<input type=\"text\" size=\"30\">");
-
- // go to the fourth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<input type=\"submit\" size=\"30\">");
-
- // go to the fifth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("End");
-
- // go to past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the fifth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("End");
-
- // go to the fourth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<input type=\"submit\" size=\"30\">");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<input type=\"text\" size=\"30\">");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<input type=\"submit\">");
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("Start");
-
- // go to before the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Start");
- }
-
- /**
- * Tests traversing of input controls.
- */
- @LargeTest
- public void testSelectionOfInputElements5() throws Exception {
- // a bit ugly but helps detect beginning and end of all tests so accessibility
- // and the mock service are not toggled on every test (expensive)
- sExecutedTestCount++;
-
- String html =
- "<!DOCTYPE html>" +
- "<html>" +
- "<head>" +
- "</head>" +
- "<body>" +
- "<div>" +
- "First" +
- "<input type=\"hidden\">" +
- "<input type=\"hidden\">" +
- "<input type=\"hidden\">" +
- "<input type=\"hidden\">" +
- "<input type=\"text\">" +
- "<span>" +
- "<span>" +
- "<input type=\"submit\">" +
- "</span>" +
- "</span>" +
- "</div>" +
- "</body>" +
- "Second" +
- "</html>";
-
- WebView webView = loadHTML(html);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<input type=\"text\">");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("<input type=\"submit\">");
-
- // go to the fourth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("Second");
-
- // go to past the last sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString(null);
-
- // go to the fourth sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("Second");
-
- // go to the third sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<input type=\"submit\">");
-
- // go to the second sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("<input type=\"text\">");
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString("First");
-
- // go to before the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0);
- assertSelectionString(null);
-
- // go to the first sentence
- sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0);
- assertSelectionString("First");
- }
-
- /**
- * Enable accessibility and the mock accessibility service.
- */
- private void enableAccessibilityAndMockAccessibilityService() {
- // make sure the manager is instantiated so the system initializes it
- AccessibilityManager.getInstance(getActivity());
-
- // enable accessibility and the mock accessibility service
- Settings.Secure.putInt(getActivity().getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, 1);
- String enabledServices = new ComponentName(getActivity().getPackageName(),
- MockAccessibilityService.class.getName()).flattenToShortString();
- Settings.Secure.putString(getActivity().getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices);
-
- // poll within a timeout and let be interrupted in case of success
- long incrementStep = TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE / 5;
- long start = SystemClock.uptimeMillis();
- while (SystemClock.uptimeMillis() - start < TIMEOUT_ENABLE_ACCESSIBILITY_AND_MOCK_SERVICE &&
- !sIsAccessibilityServiceReady) {
- synchronized (sTestLock) {
- try {
- sTestLock.wait(incrementStep);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
-
- if (!sIsAccessibilityServiceReady) {
- throw new IllegalStateException("MockAccessibilityService not ready. Did you add " +
- "tests and forgot to update AccessibilityInjectorTest#TEST_CASE_COUNT?");
- }
- }
-
- @Override
- protected void scrubClass(Class<?> testCaseClass) {
- /* do nothing - avoid superclass behavior */
- }
-
- /**
- * Strips the apple span appended by WebKit while generating
- * the selection markup.
- *
- * @param markup The markup.
- * @return Stripped from apple spans markup.
- */
- private static String stripAppleSpanFromMarkup(String markup) {
- StringBuilder stripped = new StringBuilder(markup);
- int prefixBegIdx = stripped.indexOf(APPLE_SPAN_PREFIX);
- while (prefixBegIdx >= 0) {
- int prefixEndIdx = stripped.indexOf(">", prefixBegIdx) + 1;
- stripped.replace(prefixBegIdx, prefixEndIdx, "");
- int suffixBegIdx = stripped.lastIndexOf(APPLE_SPAN_SUFFIX);
- int suffixEndIdx = suffixBegIdx + APPLE_SPAN_SUFFIX.length();
- stripped.replace(suffixBegIdx, suffixEndIdx, "");
- prefixBegIdx = stripped.indexOf(APPLE_SPAN_PREFIX);
- }
- return stripped.toString();
- }
-
- /**
- * Disables accessibility and the mock accessibility service.
- */
- private void disableAccessibilityAndMockAccessibilityService() {
- // disable accessibility and the mock accessibility service
- Settings.Secure.putInt(getActivity().getContentResolver(),
- Settings.Secure.ACCESSIBILITY_ENABLED, 0);
- Settings.Secure.putString(getActivity().getContentResolver(),
- Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
- }
-
- /**
- * Asserts the next <code>expectedSelectionString</code> to be received.
- */
- private void assertSelectionString(String expectedSelectionString) {
- assertTrue("MockAccessibilityService not ready", sIsAccessibilityServiceReady);
-
- long incrementStep = TIMEOUT_WAIT_FOR_SELECTION_STRING / 5;
- long start = SystemClock.uptimeMillis();
- while (SystemClock.uptimeMillis() - start < TIMEOUT_WAIT_FOR_SELECTION_STRING &&
- sReceivedSelectionString == SELECTION_STRING_UNKNOWN) {
- synchronized (sTestLock) {
- try {
- sTestLock.wait(incrementStep);
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- }
- try {
- if (sReceivedSelectionString == SELECTION_STRING_UNKNOWN) {
- fail("No selection string received. Expected: " + expectedSelectionString);
- }
- assertEquals(expectedSelectionString, sReceivedSelectionString);
- } finally {
- sReceivedSelectionString = SELECTION_STRING_UNKNOWN;
- }
- }
-
- /**
- * Sends a {@link KeyEvent} (up and down) to the {@link WebView}.
- *
- * @param keyCode The event key code.
- */
- private void sendKeyEvent(WebView webView, int keyCode, int metaState) {
- webView.onKeyDown(keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, keyCode, 1, metaState));
- webView.onKeyUp(keyCode, new KeyEvent(0, 0, KeyEvent.ACTION_UP, keyCode, 1, metaState));
- }
-
- /**
- * Loads HTML content in a {@link WebView}.
- *
- * @param html The HTML content;
- * @return The {@link WebView} view.
- */
- private WebView loadHTML(final String html) {
- mWorker.getHandler().post(new Runnable() {
- public void run() {
- if (mWebView == null) {
- mWebView = getActivity().getWebView();
- mWebView.setWebViewClient(new WebViewClient() {
- @Override
- public void onPageFinished(WebView view, String url) {
- mWorker.getHandler().post(new Runnable() {
- public void run() {
- synchronized (sTestLock) {
- sTestLock.notifyAll();
- }
- }
- });
- }
- });
- }
- mWebView.loadData(html, "text/html", null);
- }
- });
- synchronized (sTestLock) {
- try {
- sTestLock.wait();
- } catch (InterruptedException ie) {
- /* ignore */
- }
- }
- return mWebView;
- }
-
- /**
- * Injects web content key bindings used for testing. This is required
- * to ensure that this test will be agnostic to changes of the bindings.
- */
- private void injectTestWebContentKeyBindings() {
- ContentResolver contentResolver = getActivity().getContentResolver();
- sDefaultKeyBindings = Settings.Secure.getString(contentResolver,
- Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
- Settings.Secure.putString(contentResolver,
- Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS, TEST_KEY_DINDINGS);
- }
-
- /**
- * Restores the default web content key bindings.
- */
- private void restoreDefaultWebContentKeyBindings() {
- Settings.Secure.putString(getActivity().getContentResolver(),
- Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS,
- sDefaultKeyBindings);
- }
-
- /**
- * This is a worker thread responsible for creating the {@link WebView}.
- */
- private class Worker implements Runnable {
- private final Object mWorkerLock = new Object();
- private Handler mHandler;
-
- public Worker() {
- new Thread(this).start();
- synchronized (mWorkerLock) {
- while (mHandler == null) {
- try {
- mWorkerLock.wait();
- } catch (InterruptedException ex) {
- /* ignore */
- }
- }
- }
- }
-
- public void run() {
- synchronized (mWorkerLock) {
- Looper.prepare();
- mHandler = new Handler();
- mWorkerLock.notifyAll();
- }
- Looper.loop();
- }
-
- public Handler getHandler() {
- return mHandler;
- }
-
- public void stop() {
- mHandler.getLooper().quit();
- }
- }
-
- /**
- * Mock accessibility service to receive the accessibility events
- * with the current {@link WebView} selection.
- */
- public static class MockAccessibilityService extends AccessibilityService {
- private boolean mIsServiceInfoSet;
-
- @Override
- protected void onServiceConnected() {
- if (mIsServiceInfoSet) {
- return;
- }
- AccessibilityServiceInfo info = new AccessibilityServiceInfo();
- info.eventTypes = AccessibilityEvent.TYPE_VIEW_SELECTED;
- info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
- setServiceInfo(info);
- mIsServiceInfoSet = true;
-
- sIsAccessibilityServiceReady = true;
-
- if (sInstance == null) {
- return;
- }
- synchronized (sTestLock) {
- sTestLock.notifyAll();
- }
- }
-
- @Override
- public void onAccessibilityEvent(AccessibilityEvent event) {
- if (sInstance == null) {
- return;
- }
- if (!event.getText().isEmpty()) {
- CharSequence text = event.getText().get(0);
- if (text != null) {
- sReceivedSelectionString = stripAppleSpanFromMarkup(text.toString());
- } else {
- sReceivedSelectionString = null;
- }
- }
- synchronized (sTestLock) {
- sTestLock.notifyAll();
- }
- }
-
- @Override
- public void onInterrupt() {
- /* do nothing */
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- sIsAccessibilityServiceReady = false;
- return false;
- }
- }
-}
diff --git a/core/tests/coretests/src/android/webkit/AccessibilityInjectorTestActivity.java b/core/tests/coretests/src/android/webkit/AccessibilityInjectorTestActivity.java
deleted file mode 100644
index 3842df7..0000000
--- a/core/tests/coretests/src/android/webkit/AccessibilityInjectorTestActivity.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2011 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.webkit;
-
-import com.android.frameworks.coretests.R;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class AccessibilityInjectorTestActivity extends Activity {
-
- private WebView mWebView;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- setContentView(R.layout.accessibility_injector_test);
- mWebView = (WebView) findViewById(R.id.webview);
- }
-
- public WebView getWebView() {
- return mWebView;
- }
-}
diff --git a/core/tests/coretests/src/android/webkit/UrlInterceptRegistryTest.java b/core/tests/coretests/src/android/webkit/UrlInterceptRegistryTest.java
deleted file mode 100644
index 7504449..0000000
--- a/core/tests/coretests/src/android/webkit/UrlInterceptRegistryTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2009 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.webkit;
-
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.webkit.CacheManager.CacheResult;
-import android.webkit.PluginData;
-import android.webkit.UrlInterceptHandler;
-
-import java.util.LinkedList;
-import java.util.Map;
-
-public class UrlInterceptRegistryTest extends AndroidTestCase {
-
- /**
- * To run these tests: $ mmm
- * frameworks/base/tests/CoreTests/android && adb remount && adb
- * sync $ adb shell am instrument -w -e class \
- * android.webkit.UrlInterceptRegistryTest \
- * android.core/android.test.InstrumentationTestRunner
- */
-
- private static class MockUrlInterceptHandler implements UrlInterceptHandler {
- private PluginData mData;
- private String mUrl;
-
- public MockUrlInterceptHandler(PluginData data, String url) {
- mData = data;
- mUrl = url;
- }
-
- public CacheResult service(String url, Map<String, String> headers) {
- return null;
- }
-
- public PluginData getPluginData(String url,
- Map<String,
- String> headers) {
- if (mUrl.equals(url)) {
- return mData;
- }
-
- return null;
- }
- }
-
- public void testGetPluginData() {
- PluginData data = new PluginData(null, 0 , null, 200);
- String url = new String("url1");
- MockUrlInterceptHandler handler1 =
- new MockUrlInterceptHandler(data, url);
-
- data = new PluginData(null, 0 , null, 404);
- url = new String("url2");
- MockUrlInterceptHandler handler2 =
- new MockUrlInterceptHandler(data, url);
-
- assertTrue(UrlInterceptRegistry.registerHandler(handler1));
- assertTrue(UrlInterceptRegistry.registerHandler(handler2));
-
- data = UrlInterceptRegistry.getPluginData("url1", null);
- assertTrue(data != null);
- assertTrue(data.getStatusCode() == 200);
-
- data = UrlInterceptRegistry.getPluginData("url2", null);
- assertTrue(data != null);
- assertTrue(data.getStatusCode() == 404);
-
- assertTrue(UrlInterceptRegistry.unregisterHandler(handler1));
- assertTrue(UrlInterceptRegistry.unregisterHandler(handler2));
-
- }
-}
diff --git a/core/tests/coretests/src/android/webkit/WebkitTest.java b/core/tests/coretests/src/android/webkit/WebkitTest.java
deleted file mode 100644
index 4685e3c..0000000
--- a/core/tests/coretests/src/android/webkit/WebkitTest.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.webkit;
-
-import android.test.AndroidTestCase;
-import android.text.format.DateFormat;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.util.Log;
-import android.webkit.DateSorter;
-
-import java.util.Calendar;
-import java.util.Date;
-
-public class WebkitTest extends AndroidTestCase {
-
- private static final String LOGTAG = WebkitTest.class.getName();
-
- @MediumTest
- public void testDateSorter() throws Exception {
- /**
- * Note: check the logging output manually to test
- * nothing automated yet, besides object creation
- */
- DateSorter dateSorter = new DateSorter(mContext);
- Date date = new Date();
-
- for (int i = 0; i < DateSorter.DAY_COUNT; i++) {
- Log.i(LOGTAG, "Boundary " + i + " " + dateSorter.getBoundary(i));
- Log.i(LOGTAG, "Label " + i + " " + dateSorter.getLabel(i));
- }
-
- Calendar c = Calendar.getInstance();
- long time = c.getTimeInMillis();
- int index;
- Log.i(LOGTAG, "now: " + dateSorter.getIndex(time));
- for (int i = 0; i < 20; i++) {
- time -= 8 * 60 * 60 * 1000; // 8 hours
- date.setTime(time);
- c.setTime(date);
- index = dateSorter.getIndex(time);
- Log.i(LOGTAG, "time: " + DateFormat.format("yyyy/MM/dd HH:mm:ss", c).toString() +
- " " + index + " " + dateSorter.getLabel(index));
- }
- }
-}
diff --git a/core/tests/coretests/src/android/webkit/ZoomManagerTest.java b/core/tests/coretests/src/android/webkit/ZoomManagerTest.java
deleted file mode 100644
index 7e0e0b2..0000000
--- a/core/tests/coretests/src/android/webkit/ZoomManagerTest.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2010 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.webkit;
-
-import android.test.AndroidTestCase;
-
-public class ZoomManagerTest extends AndroidTestCase {
-
- private ZoomManager zoomManager;
-
- @Override
- public void setUp() {
- WebView webView = new WebView(this.getContext());
- WebViewClassic webViewClassic = WebViewClassic.fromWebView(webView);
- CallbackProxy callbackProxy = new CallbackProxy(this.getContext(), webViewClassic);
- zoomManager = new ZoomManager(webViewClassic, callbackProxy);
-
- zoomManager.init(1.00f);
- }
-
- public void testInit() {
- testInit(0.01f);
- testInit(1.00f);
- testInit(1.25f);
- }
-
- private void testInit(float density) {
- zoomManager.init(density);
- actualScaleTest(density);
- defaultScaleTest(density);
- assertEquals(zoomManager.getDefaultMaxZoomScale(), zoomManager.getMaxZoomScale());
- assertEquals(zoomManager.getDefaultMinZoomScale(), zoomManager.getMinZoomScale());
- assertEquals(density, zoomManager.getTextWrapScale());
- }
-
- public void testUpdateDefaultZoomDensity() {
- // test the basic case where the actual values are equal to the defaults
- testUpdateDefaultZoomDensity(0.01f);
- testUpdateDefaultZoomDensity(1.00f);
- testUpdateDefaultZoomDensity(1.25f);
- }
-
- private void testUpdateDefaultZoomDensity(float density) {
- zoomManager.updateDefaultZoomDensity(density);
- defaultScaleTest(density);
- }
-
- public void testUpdateDefaultZoomDensityWithSmallMinZoom() {
- // test the case where the minZoomScale has changed to be < the default
- float newDefaultScale = 1.50f;
- float minZoomScale = ZoomManager.DEFAULT_MIN_ZOOM_SCALE_FACTOR * newDefaultScale;
- WebViewCore.ViewState minViewState = new WebViewCore.ViewState();
- minViewState.mMinScale = minZoomScale - 0.1f;
- zoomManager.updateZoomRange(minViewState, 0, 0);
- zoomManager.updateDefaultZoomDensity(newDefaultScale);
- defaultScaleTest(newDefaultScale);
- }
-
- public void testUpdateDefaultZoomDensityWithLargeMinZoom() {
- // test the case where the minZoomScale has changed to be > the default
- float newDefaultScale = 1.50f;
- float minZoomScale = ZoomManager.DEFAULT_MIN_ZOOM_SCALE_FACTOR * newDefaultScale;
- WebViewCore.ViewState minViewState = new WebViewCore.ViewState();
- minViewState.mMinScale = minZoomScale + 0.1f;
- zoomManager.updateZoomRange(minViewState, 0, 0);
- zoomManager.updateDefaultZoomDensity(newDefaultScale);
- defaultScaleTest(newDefaultScale);
- }
-
- public void testUpdateDefaultZoomDensityWithSmallMaxZoom() {
- // test the case where the maxZoomScale has changed to be < the default
- float newDefaultScale = 1.50f;
- float maxZoomScale = ZoomManager.DEFAULT_MAX_ZOOM_SCALE_FACTOR * newDefaultScale;
- WebViewCore.ViewState maxViewState = new WebViewCore.ViewState();
- maxViewState.mMaxScale = maxZoomScale - 0.1f;
- zoomManager.updateZoomRange(maxViewState, 0, 0);
- zoomManager.updateDefaultZoomDensity(newDefaultScale);
- defaultScaleTest(newDefaultScale);
- }
-
- public void testUpdateDefaultZoomDensityWithLargeMaxZoom() {
- // test the case where the maxZoomScale has changed to be > the default
- float newDefaultScale = 1.50f;
- float maxZoomScale = ZoomManager.DEFAULT_MAX_ZOOM_SCALE_FACTOR * newDefaultScale;
- WebViewCore.ViewState maxViewState = new WebViewCore.ViewState();
- maxViewState.mMaxScale = maxZoomScale + 0.1f;
- zoomManager.updateZoomRange(maxViewState, 0, 0);
- zoomManager.updateDefaultZoomDensity(newDefaultScale);
- defaultScaleTest(newDefaultScale);
- }
-
- public void testComputeScaleWithLimits() {
- final float maxScale = zoomManager.getMaxZoomScale();
- final float minScale = zoomManager.getMinZoomScale();
- assertTrue(maxScale > minScale);
- assertEquals(maxScale, zoomManager.computeScaleWithLimits(maxScale));
- assertEquals(maxScale, zoomManager.computeScaleWithLimits(maxScale + .01f));
- assertEquals(minScale, zoomManager.computeScaleWithLimits(minScale));
- assertEquals(minScale, zoomManager.computeScaleWithLimits(minScale - .01f));
- }
-
- private void actualScaleTest(float actualScale) {
- assertEquals(actualScale, zoomManager.getScale());
- assertEquals(1 / actualScale, zoomManager.getInvScale());
- }
-
- private void defaultScaleTest(float defaultScale) {
- final float maxDefault = ZoomManager.DEFAULT_MAX_ZOOM_SCALE_FACTOR * defaultScale;
- final float minDefault = ZoomManager.DEFAULT_MIN_ZOOM_SCALE_FACTOR * defaultScale;
- assertEquals(defaultScale, zoomManager.getDefaultScale());
- assertEquals(1 / defaultScale, zoomManager.getInvDefaultScale());
- assertEquals(maxDefault, zoomManager.getDefaultMaxZoomScale());
- assertEquals(minDefault, zoomManager.getDefaultMinZoomScale());
- }
-}