diff options
Diffstat (limited to 'core')
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> - * <service ndroid:name=".MyAnonymousSyncService" android:permission="android.permission.SYNC" /> - * </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>" + - " " + - "<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(" "); - - // 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(" "); - - // 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> <small>a</small></a>" + - "</span> <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 <small>a</small></a>"); - - // go to the third sentence - sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_DOWN, 0); - assertSelectionString(" "); - - // 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(" "); - - // go to the second sentence - sendKeyEvent(webView, KeyEvent.KEYCODE_DPAD_UP, 0); - assertSelectionString("<a href=\"#\"><span>Second <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()); - } -} |