diff options
23 files changed, 723 insertions, 206 deletions
diff --git a/api/current.txt b/api/current.txt index 548433e762d5..9251d394408e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7255,6 +7255,7 @@ package android.content.pm { method public abstract java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int); method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(android.content.Intent, int); method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(android.content.ComponentName, android.content.Intent[], android.content.Intent, int); + method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int); method public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int); method public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract deprecated void removePackageFromPreferred(java.lang.String); @@ -7420,6 +7421,7 @@ package android.content.pm { ctor public ProviderInfo(); ctor public ProviderInfo(android.content.pm.ProviderInfo); method public int describeContents(); + method public void dump(android.util.Printer, java.lang.String); field public static final android.os.Parcelable.Creator CREATOR; field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000 field public java.lang.String authority; @@ -7453,6 +7455,7 @@ package android.content.pm { field public java.lang.CharSequence nonLocalizedLabel; field public int preferredOrder; field public int priority; + field public android.content.pm.ProviderInfo providerInfo; field public java.lang.String resolvePackageName; field public android.content.pm.ServiceInfo serviceInfo; field public int specificIndex; @@ -20982,6 +20985,7 @@ package android.provider { field public static final java.lang.String EXTRA_ERROR = "error"; field public static final java.lang.String EXTRA_INFO = "info"; field public static final java.lang.String EXTRA_LOADING = "loading"; + field public static final java.lang.String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER"; } public static final class DocumentsContract.Document { @@ -24477,6 +24481,7 @@ package android.test.mock { method public java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int); method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(android.content.Intent, int); method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(android.content.ComponentName, android.content.Intent[], android.content.Intent, int); + method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int); method public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int); method public java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public void removePackageFromPreferred(java.lang.String); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 55c66726c387..b505d4f8e878 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -585,6 +585,22 @@ final class ApplicationPackageManager extends PackageManager { } @Override + public List<ResolveInfo> queryIntentContentProvidersAsUser( + Intent intent, int flags, int userId) { + try { + return mPM.queryIntentContentProviders(intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), flags, userId); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) { + return queryIntentContentProvidersAsUser(intent, flags, mContext.getUserId()); + } + + @Override public ProviderInfo resolveContentProvider(String name, int flags) { try { diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index acd4ffa1a7fc..267fb2af2dd1 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -123,6 +123,9 @@ interface IPackageManager { List<ResolveInfo> queryIntentServices(in Intent intent, String resolvedType, int flags, int userId); + List<ResolveInfo> queryIntentContentProviders(in Intent intent, + String resolvedType, int flags, int userId); + /** * This implements getInstalledPackages via a "last returned row" * mechanism that is not exposed in the API. This is to get around the IPC diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b63db8a8856c..8b8c58b87076 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2210,6 +2210,24 @@ public abstract class PackageManager { public abstract List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId); + /** {@hide} */ + public abstract List<ResolveInfo> queryIntentContentProvidersAsUser( + Intent intent, int flags, int userId); + + /** + * Retrieve all providers that can match the given intent. + * + * @param intent An intent containing all of the desired specification + * (action, data, type, category, and/or component). + * @param flags Additional option flags. + * @return A List<ResolveInfo> containing one entry for each matching + * ProviderInfo. These are ordered from best to worst match. If + * there are no matching providers, an empty list is returned. + * @see #GET_INTENT_FILTERS + * @see #GET_RESOLVED_FILTER + */ + public abstract List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags); + /** * Find a single content provider by its base path name. * diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index b489ee9e2988..17d13e500e8e 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2819,7 +2819,14 @@ public class PackageParser { continue; } - if (parser.getName().equals("meta-data")) { + if (parser.getName().equals("intent-filter")) { + ProviderIntentInfo intent = new ProviderIntentInfo(outInfo); + if (!parseIntent(res, parser, attrs, true, intent, outError)) { + return false; + } + outInfo.intents.add(intent); + + } else if (parser.getName().equals("meta-data")) { if ((outInfo.metaData=parseMetaData(res, parser, attrs, outInfo.metaData, outError)) == null) { return false; @@ -3982,7 +3989,7 @@ public class PackageParser { return si; } - public final static class Provider extends Component { + public final static class Provider extends Component<ProviderIntentInfo> { public final ProviderInfo info; public boolean syncable; @@ -4116,6 +4123,24 @@ public class PackageParser { } } + public static final class ProviderIntentInfo extends IntentInfo { + public final Provider provider; + + public ProviderIntentInfo(Provider provider) { + this.provider = provider; + } + + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("ProviderIntentInfo{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(' '); + provider.appendComponentShortName(sb); + sb.append('}'); + return sb.toString(); + } + } + /** * @hide */ diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java index a53417620cc0..f6ea058b526f 100644 --- a/core/java/android/content/pm/ProviderInfo.java +++ b/core/java/android/content/pm/ProviderInfo.java @@ -19,6 +19,7 @@ package android.content.pm; import android.os.Parcel; import android.os.Parcelable; import android.os.PatternMatcher; +import android.util.Printer; /** * Holds information about a specific @@ -112,7 +113,13 @@ public final class ProviderInfo extends ComponentInfo flags = orig.flags; isSyncable = orig.isSyncable; } - + + public void dump(Printer pw, String prefix) { + super.dumpFront(pw, prefix); + pw.println(prefix + "authority=" + authority); + pw.println(prefix + "flags=0x" + Integer.toHexString(flags)); + } + public int describeContents() { return 0; } diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java index e360e4063f7a..1ff41c022186 100644 --- a/core/java/android/content/pm/ResolveInfo.java +++ b/core/java/android/content/pm/ResolveInfo.java @@ -23,6 +23,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.Printer; +import android.util.Slog; import java.text.Collator; import java.util.Comparator; @@ -34,20 +35,30 @@ import java.util.Comparator; * <intent> tags. */ public class ResolveInfo implements Parcelable { + private static final String TAG = "ResolveInfo"; + /** - * The activity or broadcast receiver that corresponds to this resolution match, - * if this resolution is for an activity or broadcast receiver. One and only one of this and - * serviceInfo must be non-null. + * The activity or broadcast receiver that corresponds to this resolution + * match, if this resolution is for an activity or broadcast receiver. + * Exactly one of {@link #activityInfo}, {@link #serviceInfo}, or + * {@link #providerInfo} will be non-null. */ public ActivityInfo activityInfo; /** - * The service that corresponds to this resolution match, if this - * resolution is for a service. One and only one of this and - * activityInfo must be non-null. + * The service that corresponds to this resolution match, if this resolution + * is for a service. Exactly one of {@link #activityInfo}, + * {@link #serviceInfo}, or {@link #providerInfo} will be non-null. */ public ServiceInfo serviceInfo; - + + /** + * The provider that corresponds to this resolution match, if this + * resolution is for a provider. Exactly one of {@link #activityInfo}, + * {@link #serviceInfo}, or {@link #providerInfo} will be non-null. + */ + public ProviderInfo providerInfo; + /** * The IntentFilter that was matched for this ResolveInfo. */ @@ -120,6 +131,13 @@ public class ResolveInfo implements Parcelable { */ public boolean system; + private ComponentInfo getComponentInfo() { + if (activityInfo != null) return activityInfo; + if (serviceInfo != null) return serviceInfo; + if (providerInfo != null) return providerInfo; + throw new IllegalStateException("Missing ComponentInfo!"); + } + /** * Retrieve the current textual label associated with this resolution. This * will call back on the given PackageManager to load the label from @@ -142,7 +160,7 @@ public class ResolveInfo implements Parcelable { return label.toString().trim(); } } - ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo; + ComponentInfo ci = getComponentInfo(); ApplicationInfo ai = ci.applicationInfo; if (labelRes != 0) { label = pm.getText(ci.packageName, labelRes, ai); @@ -176,7 +194,7 @@ public class ResolveInfo implements Parcelable { return dr; } } - ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo; + ComponentInfo ci = getComponentInfo(); ApplicationInfo ai = ci.applicationInfo; if (icon != 0) { dr = pm.getDrawable(ci.packageName, icon, ai); @@ -196,8 +214,8 @@ public class ResolveInfo implements Parcelable { */ public final int getIconResource() { if (icon != 0) return icon; - if (activityInfo != null) return activityInfo.getIconResource(); - if (serviceInfo != null) return serviceInfo.getIconResource(); + final ComponentInfo ci = getComponentInfo(); + if (ci != null) return ci.getIconResource(); return 0; } @@ -225,6 +243,9 @@ public class ResolveInfo implements Parcelable { } else if (serviceInfo != null) { pw.println(prefix + "ServiceInfo:"); serviceInfo.dump(pw, prefix + " "); + } else if (providerInfo != null) { + pw.println(prefix + "ProviderInfo:"); + providerInfo.dump(pw, prefix + " "); } } @@ -234,6 +255,7 @@ public class ResolveInfo implements Parcelable { public ResolveInfo(ResolveInfo orig) { activityInfo = orig.activityInfo; serviceInfo = orig.serviceInfo; + providerInfo = orig.providerInfo; filter = orig.filter; priority = orig.priority; preferredOrder = orig.preferredOrder; @@ -247,7 +269,7 @@ public class ResolveInfo implements Parcelable { } public String toString() { - ComponentInfo ci = activityInfo != null ? activityInfo : serviceInfo; + final ComponentInfo ci = getComponentInfo(); StringBuilder sb = new StringBuilder(128); sb.append("ResolveInfo{"); sb.append(Integer.toHexString(System.identityHashCode(this))); @@ -278,6 +300,9 @@ public class ResolveInfo implements Parcelable { } else if (serviceInfo != null) { dest.writeInt(2); serviceInfo.writeToParcel(dest, parcelableFlags); + } else if (providerInfo != null) { + dest.writeInt(3); + providerInfo.writeToParcel(dest, parcelableFlags); } else { dest.writeInt(0); } @@ -309,18 +334,21 @@ public class ResolveInfo implements Parcelable { }; private ResolveInfo(Parcel source) { + activityInfo = null; + serviceInfo = null; + providerInfo = null; switch (source.readInt()) { case 1: activityInfo = ActivityInfo.CREATOR.createFromParcel(source); - serviceInfo = null; break; case 2: serviceInfo = ServiceInfo.CREATOR.createFromParcel(source); - activityInfo = null; + break; + case 3: + providerInfo = ProviderInfo.CREATOR.createFromParcel(source); break; default: - activityInfo = null; - serviceInfo = null; + Slog.w(TAG, "Missing ComponentInfo!"); break; } if (source.readInt() != 0) { diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index a9a72b074df8..7095e4d498ab 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -197,26 +197,33 @@ public interface CameraDevice extends AutoCloseable { * if the format is user-visible, it must be one of android.scaler.availableFormats; * and the size must be one of android.scaler.available[Processed|Jpeg]Sizes).</p> * - * <p>To change the output, the camera device must be idle. The device is considered - * to be idle once all in-flight and pending capture requests have been processed, - * and all output image buffers from the captures have been sent to their destination - * Surfaces.</p> - * - * <p>To reach an idle state without cancelling any submitted captures, first - * stop any repeating request/burst with {@link #stopRepeating}, and then - * wait for the {@link StateListener#onIdle} callback to be - * called. To idle as fast as possible, use {@link #flush} and wait for the - * idle callback.</p> + * <p>When this method is called with valid Surfaces, the device will transition to the {@link + * StateListener#onBusy busy state}. Once configuration is complete, the device will transition + * into the {@link StateListener#onIdle idle state}. Capture requests using the newly-configured + * Surfaces may then be submitted with {@link #capture}, {@link #captureBurst}, {@link + * #setRepeatingRequest}, or {@link #setRepeatingBurst}.</p> + * + * <p>If this method is called while the camera device is still actively processing previously + * submitted captures, then the following sequence of events occurs: The device transitions to + * the busy state and calls the {@link StateListener#onBusy} callback. Second, if a repeating + * request is set it is cleared. Third, the device finishes up all in-flight and pending + * requests. Finally, once the device is idle, it then reconfigures its outputs, and calls the + * {@link StateListener#onIdle} method once it is again ready to accept capture + * requests. Therefore, no submitted work is discarded. To idle as fast as possible, use {@link + * #flush} and wait for the idle callback before calling configureOutputs. This will discard + * work, but reaches the new configuration sooner.</p> * * <p>Using larger resolution outputs, or more outputs, can result in slower * output rate from the device.</p> * - * <p>Configuring the outputs with an empty or null list will transition - * the camera into an {@link StateListener#onUnconfigured unconfigured state}. - * </p> + * <p>Configuring the outputs with an empty or null list will transition the camera into an + * {@link StateListener#onUnconfigured unconfigured state} instead of the {@link + * StateListener#onIdle idle state}. </p> * * <p>Calling configureOutputs with the same arguments as the last call to - * configureOutputs has no effect.</p> + * configureOutputs has no effect, and the {@link StateListener#onBusy busy} + * and {@link StateListener#onIdle idle} state transitions will happen + * immediately.</p> * * @param outputs The new set of Surfaces that should be made available as * targets for captured image data. @@ -228,7 +235,10 @@ public interface CameraDevice extends AutoCloseable { * @throws IllegalStateException if the camera device is not idle, or * if the camera device has been closed * + * @see StateListener#onBusy * @see StateListener#onIdle + * @see StateListener#onActive + * @see StateListener#onUnconfigured * @see #stopRepeating * @see #flush */ @@ -516,31 +526,6 @@ public interface CameraDevice extends AutoCloseable { public void waitUntilIdle() throws CameraAccessException; /** - * Set the listener object to call when an asynchronous device event occurs, - * such as errors or idle notifications. - * - * <p>The events reported here are device-wide; notifications about - * individual capture requests or capture results are reported through - * {@link CaptureListener}.</p> - * - * <p>If the camera device is idle when the listener is set, then the - * {@link StateListener#onIdle} method will be immediately called, - * even if the device has never been active before. - * </p> - * - * @param listener the CameraDeviceListener to send device-level event - * notifications to. Setting this to null will stop notifications. - * @param handler the handler on which the listener should be invoked, or - * {@code null} to use the current thread's {@link android.os.Looper looper}. - * - * @throws IllegalArgumentException if handler is null, the listener is - * not null, and the calling thread has no looper - * - * @hide - */ - public void setDeviceListener(StateListener listener, Handler handler); - - /** * Flush all captures currently pending and in-progress as fast as * possible. * @@ -577,13 +562,24 @@ public interface CameraDevice extends AutoCloseable { public void flush() throws CameraAccessException; /** - * Close the connection to this camera device. After this call, all calls to + * Close the connection to this camera device. + * + * <p>After this call, all calls to * the camera device interface will throw a {@link IllegalStateException}, - * except for calls to close(). + * except for calls to close(). Once the device has fully shut down, the + * {@link StateListener#onClosed} callback will be called, and the camera is + * free to be re-opened.</p> + * + * <p>After this call, besides the final {@link StateListener#onClosed} call, no calls to the + * device's {@link StateListener} will occur, and any remaining submitted capture requests will + * not fire their {@link CaptureListener} callbacks.</p> + * + * <p>To shut down as fast as possible, call the {@link #flush} method and then {@link #close} + * once the flush completes. This will discard some capture requests, but results in faster + * shutdown.</p> */ @Override public void close(); - // TODO: We should decide on the behavior of in-flight requests should be on close. /** * <p>A listener for tracking the progress of a {@link CaptureRequest} @@ -713,6 +709,9 @@ public interface CameraDevice extends AutoCloseable { * A listener for notifications about the state of a camera * device. * + * <p>A listener must be provided to the {@link CameraManager#openCamera} + * method to open a camera device.</p> + * * <p>These events include notifications about the device becoming idle ( * allowing for {@link #configureOutputs} to be called), about device * disconnection, and about unexpected device errors.</p> @@ -722,7 +721,7 @@ public interface CameraDevice extends AutoCloseable { * the {@link #capture}, {@link #captureBurst}, {@link * #setRepeatingRequest}, or {@link #setRepeatingBurst} methods. * - * @see #setDeviceListener + * @see CameraManager#openCamera */ public static abstract class StateListener { /** diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index f5ee367c70ca..65b6c7a09443 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -197,6 +197,8 @@ public final class CameraManager { * {@link #openCamera}. * * @param cameraId The unique identifier of the camera device to open + * @param listener The listener for the camera. Must not be null. + * @param handler The handler to call the listener on. Must not be null. * * @throws CameraAccessException if the camera is disabled by device policy, * or too many camera devices are already open, or the cameraId does not match @@ -204,11 +206,14 @@ public final class CameraManager { * * @throws SecurityException if the application does not have permission to * access the camera + * @throws IllegalArgumentException if listener or handler is null. * * @see #getCameraIdList * @see android.app.admin.DevicePolicyManager#setCameraDisabled */ - private CameraDevice openCamera(String cameraId) throws CameraAccessException { + private void openCameraDeviceUserAsync(String cameraId, + CameraDevice.StateListener listener, Handler handler) + throws CameraAccessException { try { synchronized (mLock) { @@ -216,7 +221,10 @@ public final class CameraManager { ICameraDeviceUser cameraUser; android.hardware.camera2.impl.CameraDevice device = - new android.hardware.camera2.impl.CameraDevice(cameraId); + new android.hardware.camera2.impl.CameraDevice( + cameraId, + listener, + handler); BinderHolder holder = new BinderHolder(); mCameraService.connectDevice(device.getCallbacks(), @@ -225,10 +233,9 @@ public final class CameraManager { cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); // TODO: factor out listener to be non-nested, then move setter to constructor + // For now, calling setRemoteDevice will fire initial + // onOpened/onUnconfigured callbacks. device.setRemoteDevice(cameraUser); - - return device; - } } catch (NumberFormatException e) { @@ -238,7 +245,6 @@ public final class CameraManager { throw e.asChecked(); } catch (RemoteException e) { // impossible - return null; } } @@ -303,16 +309,7 @@ public final class CameraManager { } } - final CameraDevice camera = openCamera(cameraId); - camera.setDeviceListener(listener, handler); - - // TODO: make truly async in the camera service - handler.post(new Runnable() { - @Override - public void run() { - listener.onOpened(camera); - } - }); + openCameraDeviceUserAsync(cameraId, listener, handler); } /** diff --git a/core/java/android/hardware/camera2/Face.java b/core/java/android/hardware/camera2/Face.java index 4ac04dd40ceb..ded8839dc15c 100644 --- a/core/java/android/hardware/camera2/Face.java +++ b/core/java/android/hardware/camera2/Face.java @@ -58,7 +58,7 @@ public final class Face { * Create a new face with all fields set. * * <p>The id, leftEyePosition, rightEyePosition, and mouthPosition are considered optional. - * They are only required when the {@link #CaptureResult} reports that the value of key + * They are only required when the {@link CaptureResult} reports that the value of key * {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} is * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_FULL}. * If the id is {@value #ID_UNSUPPORTED} then the leftEyePosition, rightEyePosition, and @@ -112,7 +112,7 @@ public final class Face { * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, * rightEyePosition, and mouthPosition may be independently null or not-null. When devices * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as - * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link #CaptureResult}, + * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult}, * the face id of each face is expected to be {@value #ID_UNSUPPORTED}, the leftEyePosition, * rightEyePosition, and mouthPositions are expected to be {@code null} for each face.</p> * @@ -177,7 +177,7 @@ public final class Face { * mouthPositions are guaranteed to be {@code null}. Otherwise, each of leftEyePosition, * rightEyePosition, and mouthPosition may be independently null or not-null. When devices * report the value of key {@link CaptureResult#STATISTICS_FACE_DETECT_MODE} as - * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link #CaptureResult}, + * {@link CameraMetadata#STATISTICS_FACE_DETECT_MODE_SIMPLE} in {@link CaptureResult}, * the face id of each face is expected to be {@value #ID_UNSUPPORTED}.</p> * * <p>This value will either be {@value #ID_UNSUPPORTED} or diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java index 463063c1fb2a..c5d099921306 100644 --- a/core/java/android/hardware/camera2/impl/CameraDevice.java +++ b/core/java/android/hardware/camera2/impl/CameraDevice.java @@ -55,8 +55,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final Object mLock = new Object(); private final CameraDeviceCallbacks mCallbacks = new CameraDeviceCallbacks(); - private StateListener mDeviceListener; - private Handler mDeviceHandler; + private final StateListener mDeviceListener; + private final Handler mDeviceHandler; + + private boolean mIdle = true; private final SparseArray<CaptureListenerHolder> mCaptureListenerMap = new SparseArray<CaptureListenerHolder>(); @@ -67,8 +69,72 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { private final String mCameraId; - public CameraDevice(String cameraId) { + // Runnables for all state transitions, except error, which needs the + // error code argument + + private final Runnable mCallOnOpened = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onOpened(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnUnconfigured = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onUnconfigured(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnActive = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onActive(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnBusy = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onBusy(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnClosed = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onClosed(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnIdle = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onIdle(CameraDevice.this); + } + } + }; + + private final Runnable mCallOnDisconnected = new Runnable() { + public void run() { + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onDisconnected(CameraDevice.this); + } + } + }; + + public CameraDevice(String cameraId, StateListener listener, Handler handler) { + if (cameraId == null || listener == null || handler == null) { + throw new IllegalArgumentException("Null argument given"); + } mCameraId = cameraId; + mDeviceListener = listener; + mDeviceHandler = handler; TAG = String.format("CameraDevice-%s-JV", mCameraId); DEBUG = Log.isLoggable(TAG, Log.DEBUG); } @@ -79,7 +145,12 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { public void setRemoteDevice(ICameraDeviceUser remoteDevice) { // TODO: Move from decorator to direct binder-mediated exceptions - mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice); + synchronized(mLock) { + mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice); + + mDeviceHandler.post(mCallOnOpened); + mDeviceHandler.post(mCallOnUnconfigured); + } } @Override @@ -89,7 +160,13 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { @Override public void configureOutputs(List<Surface> outputs) throws CameraAccessException { + // Treat a null input the same an empty list + if (outputs == null) { + outputs = new ArrayList<Surface>(); + } synchronized (mLock) { + checkIfCameraClosed(); + HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create List<Integer> deleteList = new ArrayList<Integer>(); // Streams to delete @@ -105,9 +182,13 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } } + mDeviceHandler.post(mCallOnBusy); + stopRepeating(); + try { - // TODO: mRemoteDevice.beginConfigure + mRemoteDevice.waitUntilIdle(); + // TODO: mRemoteDevice.beginConfigure // Delete all streams first (to free up HW resources) for (Integer streamId : deleteList) { mRemoteDevice.deleteStream(streamId); @@ -126,7 +207,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } catch (CameraRuntimeException e) { if (e.getReason() == CAMERA_IN_USE) { throw new IllegalStateException("The camera is currently busy." + - " You must call waitUntilIdle before trying to reconfigure."); + " You must wait until the previous operation completes."); } throw e.asChecked(); @@ -134,6 +215,12 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { // impossible return; } + + if (outputs.size() > 0) { + mDeviceHandler.post(mCallOnIdle); + } else { + mDeviceHandler.post(mCallOnUnconfigured); + } } } @@ -141,6 +228,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { public CaptureRequest.Builder createCaptureRequest(int templateType) throws CameraAccessException { synchronized (mLock) { + checkIfCameraClosed(); CameraMetadataNative templatedRequest = new CameraMetadataNative(); @@ -188,7 +276,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } synchronized (mLock) { - + checkIfCameraClosed(); int requestId; try { @@ -208,6 +296,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { mRepeatingRequestIdStack.add(requestId); } + if (mIdle) { + mDeviceHandler.post(mCallOnActive); + } + mIdle = false; + return requestId; } } @@ -233,7 +326,7 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { public void stopRepeating() throws CameraAccessException { synchronized (mLock) { - + checkIfCameraClosed(); while (!mRepeatingRequestIdStack.isEmpty()) { int requestId = mRepeatingRequestIdStack.pop(); @@ -270,20 +363,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { } @Override - public void setDeviceListener(StateListener listener, Handler handler) { - synchronized (mLock) { - if (listener != null) { - handler = checkHandler(handler); - } - - mDeviceListener = listener; - mDeviceHandler = handler; - } - } - - @Override public void flush() throws CameraAccessException { synchronized (mLock) { + checkIfCameraClosed(); + + mDeviceHandler.post(mCallOnBusy); try { mRemoteDevice.flush(); } catch (CameraRuntimeException e) { @@ -297,9 +381,6 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { @Override public void close() { - - // TODO: every method should throw IllegalStateException after close has been called - synchronized (mLock) { try { @@ -312,8 +393,11 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { // impossible } - mRemoteDevice = null; + if (mRemoteDevice != null) { + mDeviceHandler.post(mCallOnClosed); + } + mRemoteDevice = null; } } @@ -399,49 +483,44 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { @Override public void onCameraError(final int errorCode) { - synchronized (mLock) { - if (CameraDevice.this.mDeviceListener == null) return; - final StateListener listener = CameraDevice.this.mDeviceListener; - Runnable r = null; + Runnable r = null; + if (isClosed()) return; + + synchronized(mLock) { switch (errorCode) { case ERROR_CAMERA_DISCONNECTED: - r = new Runnable() { - public void run() { - listener.onDisconnected(CameraDevice.this); - } - }; + r = mCallOnDisconnected; break; + default: + Log.e(TAG, "Unknown error from camera device: " + errorCode); + // no break case ERROR_CAMERA_DEVICE: case ERROR_CAMERA_SERVICE: r = new Runnable() { public void run() { - listener.onError(CameraDevice.this, errorCode); + if (!CameraDevice.this.isClosed()) { + mDeviceListener.onError(CameraDevice.this, errorCode); + } } }; break; - default: - Log.e(TAG, "Unknown error from camera device: " + errorCode); - } - if (r != null) { - CameraDevice.this.mDeviceHandler.post(r); } + CameraDevice.this.mDeviceHandler.post(r); } } @Override public void onCameraIdle() { + if (isClosed()) return; + if (DEBUG) { Log.d(TAG, "Camera now idle"); } synchronized (mLock) { - if (CameraDevice.this.mDeviceListener == null) return; - final StateListener listener = CameraDevice.this.mDeviceListener; - Runnable r = new Runnable() { - public void run() { - listener.onIdle(CameraDevice.this); - } - }; - CameraDevice.this.mDeviceHandler.post(r); + if (!CameraDevice.this.mIdle) { + CameraDevice.this.mDeviceHandler.post(mCallOnIdle); + } + CameraDevice.this.mIdle = true; } } @@ -461,14 +540,18 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { return; } + if (isClosed()) return; + // Dispatch capture start notice holder.getHandler().post( new Runnable() { public void run() { - holder.getListener().onCaptureStarted( - CameraDevice.this, - holder.getRequest(), - timestamp); + if (!CameraDevice.this.isClosed()) { + holder.getListener().onCaptureStarted( + CameraDevice.this, + holder.getRequest(), + timestamp); + } } }); } @@ -503,6 +586,8 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { return; } + if (isClosed()) return; + final CaptureRequest request = holder.getRequest(); final CaptureResult resultAsCapture = new CaptureResult(result, request, requestId); @@ -510,10 +595,12 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { new Runnable() { @Override public void run() { - holder.getListener().onCaptureCompleted( - CameraDevice.this, - request, - resultAsCapture); + if (!CameraDevice.this.isClosed()){ + holder.getListener().onCaptureCompleted( + CameraDevice.this, + request, + resultAsCapture); + } } }); } @@ -541,4 +628,10 @@ public class CameraDevice implements android.hardware.camera2.CameraDevice { throw new IllegalStateException("CameraDevice was already closed"); } } + + private boolean isClosed() { + synchronized(mLock) { + return (mRemoteDevice == null); + } + } } diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java index ec979b3a2d4d..e1a9cb7592e5 100644 --- a/core/java/android/print/PrintAttributes.java +++ b/core/java/android/print/PrintAttributes.java @@ -282,7 +282,7 @@ public final class PrintAttributes implements Parcelable { */ public static final MediaSize UNKNOWN_PORTRAIT = new MediaSize("UNKNOWN_PORTRAIT", "android", - R.string.mediasize_unknown_portrait, Integer.MAX_VALUE, 1); + R.string.mediasize_unknown_portrait, 1, Integer.MAX_VALUE); /** * Unknown media size in landscape mode. @@ -293,7 +293,7 @@ public final class PrintAttributes implements Parcelable { */ public static final MediaSize UNKNOWN_LANDSCAPE = new MediaSize("UNKNOWN_LANDSCAPE", "android", - R.string.mediasize_unknown_landscape, 1, Integer.MAX_VALUE); + R.string.mediasize_unknown_landscape, Integer.MAX_VALUE, 1); // ISO sizes diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 631a8d41c064..1c14c38f840b 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -70,8 +70,14 @@ public final class DocumentsContract { } /** {@hide} */ + @Deprecated public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER"; + /** + * Intent action used to identify {@link DocumentsProvider} instances. + */ + public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER"; + /** {@hide} */ public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT"; /** {@hide} */ diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java index ef858eb0c0bf..5b04a9132612 100644 --- a/graphics/java/android/graphics/Path.java +++ b/graphics/java/android/graphics/Path.java @@ -78,7 +78,11 @@ public class Path { mLastDirection = null; if (rects != null) rects.setEmpty(); } + // We promised not to change this, so preserve it around the native + // call, which does now reset fill type. + final FillType fillType = getFillType(); native_reset(mNativePath); + setFillType(fillType); } /** diff --git a/graphics/tests/graphicstests/src/android/graphics/PathTest.java b/graphics/tests/graphicstests/src/android/graphics/PathTest.java new file mode 100644 index 000000000000..96200bc099f9 --- /dev/null +++ b/graphics/tests/graphicstests/src/android/graphics/PathTest.java @@ -0,0 +1,37 @@ +/* + * 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.graphics; + +import android.test.suitebuilder.annotation.SmallTest; +import junit.framework.TestCase; + + +public class PathTest extends TestCase { + + @SmallTest + public void testResetPreservesFillType() throws Exception { + Path path = new Path(); + + final Path.FillType defaultFillType = path.getFillType(); + final Path.FillType fillType = Path.FillType.INVERSE_EVEN_ODD; + assertFalse(fillType.equals(defaultFillType)); // Sanity check for the test itself. + + path.setFillType(fillType); + path.reset(); + assertEquals(path.getFillType(), fillType); + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 4d410d53ee96..1f3901c7bd24 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -295,6 +295,11 @@ public class DirectoryFragment extends Fragment { updateDisplayState(); + // When launched into empty recents, show drawer + if (mType == TYPE_RECENT_OPEN && mAdapter.isEmpty() && !state.stackTouched) { + ((DocumentsActivity) context).setRootsDrawerOpen(true); + } + // Restore any previous instance state final SparseArray<Parcelable> container = state.dirState.remove(mStateKey); if (container != null && !getArguments().getBoolean(EXTRA_IGNORE_STATE, false)) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index 39541731eda5..05766f5409d4 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -109,6 +109,11 @@ public class RecentsCreateFragment extends Fragment { public void onLoadFinished( Loader<List<DocumentStack>> loader, List<DocumentStack> data) { mAdapter.swapStacks(data); + + // When launched into empty recents, show drawer + if (mAdapter.isEmpty() && !state.stackTouched) { + ((DocumentsActivity) context).setRootsDrawerOpen(true); + } } @Override diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index bad0a9605a1b..eb5676509840 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -21,9 +21,11 @@ import static com.android.documentsui.DocumentsActivity.TAG; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; @@ -158,6 +160,9 @@ public class RootsCache { private class UpdateTask extends AsyncTask<Void, Void, Void> { private final String mFilterPackage; + private final Multimap<String, RootInfo> mTaskRoots = ArrayListMultimap.create(); + private final HashSet<String> mTaskStoppedAuthorities = Sets.newHashSet(); + /** * Update all roots. */ @@ -177,54 +182,64 @@ public class RootsCache { protected Void doInBackground(Void... params) { final long start = SystemClock.elapsedRealtime(); - final Multimap<String, RootInfo> roots = ArrayListMultimap.create(); - final HashSet<String> stoppedAuthorities = Sets.newHashSet(); - - roots.put(mRecentsRoot.authority, mRecentsRoot); + mTaskRoots.put(mRecentsRoot.authority, mRecentsRoot); final ContentResolver resolver = mContext.getContentResolver(); final PackageManager pm = mContext.getPackageManager(); - final List<ProviderInfo> providers = pm.queryContentProviders( + + // Pick up provider with action string + final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); + final List<ResolveInfo> providers = pm.queryIntentContentProviders(intent, 0); + for (ResolveInfo info : providers) { + handleDocumentsProvider(info.providerInfo); + } + + // Pick up legacy providers + final List<ProviderInfo> legacyProviders = pm.queryContentProviders( null, -1, PackageManager.GET_META_DATA); - for (ProviderInfo info : providers) { + for (ProviderInfo info : legacyProviders) { if (info.metaData != null && info.metaData.containsKey( DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) { - // Ignore stopped packages for now; we might query them - // later during UI interaction. - if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) { - if (LOGD) Log.d(TAG, "Ignoring stopped authority " + info.authority); - stoppedAuthorities.add(info.authority); - continue; - } - - // Try using cached roots if filtering - boolean cacheHit = false; - if (mFilterPackage != null && !mFilterPackage.equals(info.packageName)) { - synchronized (mLock) { - if (roots.putAll(info.authority, mRoots.get(info.authority))) { - if (LOGD) Log.d(TAG, "Used cached roots for " + info.authority); - cacheHit = true; - } - } - } - - // Cache miss, or loading everything - if (!cacheHit) { - roots.putAll( - info.authority, loadRootsForAuthority(resolver, info.authority)); - } + handleDocumentsProvider(info); } } final long delta = SystemClock.elapsedRealtime() - start; - Log.d(TAG, "Update found " + roots.size() + " roots in " + delta + "ms"); + Log.d(TAG, "Update found " + mTaskRoots.size() + " roots in " + delta + "ms"); synchronized (mLock) { - mStoppedAuthorities = stoppedAuthorities; - mRoots = roots; + mRoots = mTaskRoots; + mStoppedAuthorities = mTaskStoppedAuthorities; } mFirstLoad.countDown(); return null; } + + private void handleDocumentsProvider(ProviderInfo info) { + // Ignore stopped packages for now; we might query them + // later during UI interaction. + if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) { + if (LOGD) Log.d(TAG, "Ignoring stopped authority " + info.authority); + mTaskStoppedAuthorities.add(info.authority); + return; + } + + // Try using cached roots if filtering + boolean cacheHit = false; + if (mFilterPackage != null && !mFilterPackage.equals(info.packageName)) { + synchronized (mLock) { + if (mTaskRoots.putAll(info.authority, mRoots.get(info.authority))) { + if (LOGD) Log.d(TAG, "Used cached roots for " + info.authority); + cacheHit = true; + } + } + } + + // Cache miss, or loading everything + if (!cacheHit) { + mTaskRoots.putAll(info.authority, + loadRootsForAuthority(mContext.getContentResolver(), info.authority)); + } + } } /** diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index 2fb12bb29233..fdbc3abf6187 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -142,9 +142,12 @@ public class RootsFragment extends Fragment { final RootInfo root = ((DocumentsActivity) getActivity()).getCurrentRoot(); for (int i = 0; i < mAdapter.getCount(); i++) { final Object item = mAdapter.getItem(i); - if (Objects.equal(item, root)) { - mList.setItemChecked(i, true); - return; + if (item instanceof RootItem) { + final RootInfo testRoot = ((RootItem) item).root; + if (Objects.equal(testRoot, root)) { + mList.setItemChecked(i, true); + return; + } } } } diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index 7094efc375b6..99a42600cc5a 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -11,9 +11,9 @@ android:grantUriPermissions="true" android:exported="true" android:permission="android.permission.MANAGE_DOCUMENTS"> - <meta-data - android:name="android.content.DOCUMENT_PROVIDER" - android:value="true" /> + <intent-filter> + <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> + </intent-filter> </provider> <!-- TODO: find a better place for tests to live --> @@ -24,9 +24,9 @@ android:exported="true" android:permission="android.permission.MANAGE_DOCUMENTS" android:enabled="false"> - <meta-data - android:name="android.content.DOCUMENT_PROVIDER" - android:value="true" /> + <intent-filter> + <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> + </intent-filter> </provider> </application> </manifest> diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java index 27ca7a05d9c4..b69a0c8185b6 100644 --- a/services/java/com/android/server/am/ActiveServices.java +++ b/services/java/com/android/server/am/ActiveServices.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import android.os.Handler; +import android.os.Looper; import android.util.ArrayMap; import com.android.internal.app.ProcessStats; import com.android.internal.os.BatteryStatsImpl; @@ -166,7 +167,8 @@ public final class ActiveServices { static final int MSG_BG_START_TIMEOUT = 1; - ServiceMap(int userId) { + ServiceMap(Looper looper, int userId) { + super(looper); mUserId = userId; } @@ -255,7 +257,7 @@ public final class ActiveServices { private ServiceMap getServiceMap(int callingUser) { ServiceMap smap = mServiceMap.get(callingUser); if (smap == null) { - smap = new ServiceMap(callingUser); + smap = new ServiceMap(mAm.mHandler.getLooper(), callingUser); mServiceMap.put(callingUser, smap); } return smap; @@ -2417,7 +2419,11 @@ public final class ActiveServices { int[] users = mAm.getUsersLocked(); if ("all".equals(name)) { for (int user : users) { - ArrayMap<ComponentName, ServiceRecord> alls = getServices(user); + ServiceMap smap = mServiceMap.get(user); + if (smap == null) { + continue; + } + ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName; for (int i=0; i<alls.size(); i++) { ServiceRecord r1 = alls.valueAt(i); services.add(r1); @@ -2438,7 +2444,11 @@ public final class ActiveServices { } for (int user : users) { - ArrayMap<ComponentName, ServiceRecord> alls = getServices(user); + ServiceMap smap = mServiceMap.get(user); + if (smap == null) { + continue; + } + ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName; for (int i=0; i<alls.size(); i++) { ServiceRecord r1 = alls.valueAt(i); if (componentName != null) { diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index e84f90ec4b5c..fb6e9edcbb6c 100755 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -383,13 +383,12 @@ public class PackageManagerService extends IPackageManager.Stub { // All available services, for your resolving pleasure. final ServiceIntentResolver mServices = new ServiceIntentResolver(); - // Keys are String (provider class name), values are Provider. - final HashMap<ComponentName, PackageParser.Provider> mProvidersByComponent = - new HashMap<ComponentName, PackageParser.Provider>(); + // All available providers, for your resolving pleasure. + final ProviderIntentResolver mProviders = new ProviderIntentResolver(); // Mapping from provider base names (first directory in content URI codePath) // to the provider information. - final HashMap<String, PackageParser.Provider> mProviders = + final HashMap<String, PackageParser.Provider> mProvidersByAuthority = new HashMap<String, PackageParser.Provider>(); // Mapping from instrumentation class names to info about them. @@ -2095,7 +2094,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (!sUserManager.exists(userId)) return null; enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get provider info"); synchronized (mPackages) { - PackageParser.Provider p = mProvidersByComponent.get(component); + PackageParser.Provider p = mProviders.mProviders.get(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getProviderInfo " + component + ": " + p); if (p != null && mSettings.isEnabledLPr(p.info, flags, userId)) { @@ -3121,6 +3120,43 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public List<ResolveInfo> queryIntentContentProviders( + Intent intent, String resolvedType, int flags, int userId) { + if (!sUserManager.exists(userId)) return Collections.emptyList(); + ComponentName comp = intent.getComponent(); + if (comp == null) { + if (intent.getSelector() != null) { + intent = intent.getSelector(); + comp = intent.getComponent(); + } + } + if (comp != null) { + final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); + final ProviderInfo pi = getProviderInfo(comp, flags, userId); + if (pi != null) { + final ResolveInfo ri = new ResolveInfo(); + ri.providerInfo = pi; + list.add(ri); + } + return list; + } + + // reader + synchronized (mPackages) { + String pkgName = intent.getPackage(); + if (pkgName == null) { + return mProviders.queryIntent(intent, resolvedType, flags, userId); + } + final PackageParser.Package pkg = mPackages.get(pkgName); + if (pkg != null) { + return mProviders.queryIntentForPackage( + intent, resolvedType, flags, pkg.providers, userId); + } + return null; + } + } + + @Override public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) { final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; @@ -3293,7 +3329,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (!sUserManager.exists(userId)) return null; // reader synchronized (mPackages) { - final PackageParser.Provider provider = mProviders.get(name); + final PackageParser.Provider provider = mProvidersByAuthority.get(name); PackageSetting ps = provider != null ? mSettings.mPackages.get(provider.owner.packageName) : null; @@ -3314,8 +3350,8 @@ public class PackageManagerService extends IPackageManager.Stub { public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo) { // reader synchronized (mPackages) { - final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProviders.entrySet() - .iterator(); + final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProvidersByAuthority + .entrySet().iterator(); final int userId = UserHandle.getCallingUserId(); while (i.hasNext()) { Map.Entry<String, PackageParser.Provider> entry = i.next(); @@ -3341,7 +3377,7 @@ public class PackageManagerService extends IPackageManager.Stub { ArrayList<ProviderInfo> finalList = null; // reader synchronized (mPackages) { - final Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator(); + final Iterator<PackageParser.Provider> i = mProviders.mProviders.values().iterator(); final int userId = processName != null ? UserHandle.getUserId(uid) : UserHandle.getCallingUserId(); while (i.hasNext()) { @@ -4313,8 +4349,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (p.info.authority != null) { String names[] = p.info.authority.split(";"); for (int j = 0; j < names.length; j++) { - if (mProviders.containsKey(names[j])) { - PackageParser.Provider other = mProviders.get(names[j]); + if (mProvidersByAuthority.containsKey(names[j])) { + PackageParser.Provider other = mProvidersByAuthority.get(names[j]); Slog.w(TAG, "Can't install because provider name " + names[j] + " (in package " + pkg.applicationInfo.packageName + ") is already used by " @@ -4745,8 +4781,7 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Provider p = pkg.providers.get(i); p.info.processName = fixProcessName(pkg.applicationInfo.processName, p.info.processName, pkg.applicationInfo.uid); - mProvidersByComponent.put(new ComponentName(p.info.packageName, - p.info.name), p); + mProviders.addProvider(p); p.syncable = p.info.isSyncable; if (p.info.authority != null) { String names[] = p.info.authority.split(";"); @@ -4763,8 +4798,8 @@ public class PackageManagerService extends IPackageManager.Stub { p = new PackageParser.Provider(p); p.syncable = false; } - if (!mProviders.containsKey(names[j])) { - mProviders.put(names[j], p); + if (!mProvidersByAuthority.containsKey(names[j])) { + mProvidersByAuthority.put(names[j], p); if (p.info.authority == null) { p.info.authority = names[j]; } else { @@ -4777,7 +4812,7 @@ public class PackageManagerService extends IPackageManager.Stub { + p.info.isSyncable); } } else { - PackageParser.Provider other = mProviders.get(names[j]); + PackageParser.Provider other = mProvidersByAuthority.get(names[j]); Slog.w(TAG, "Skipping provider name " + names[j] + " (in package " + pkg.applicationInfo.packageName + "): name already used by " @@ -5108,8 +5143,7 @@ public class PackageManagerService extends IPackageManager.Stub { int i; for (i=0; i<N; i++) { PackageParser.Provider p = pkg.providers.get(i); - mProvidersByComponent.remove(new ComponentName(p.info.packageName, - p.info.name)); + mProviders.removeProvider(p); if (p.info.authority == null) { /* There was another ContentProvider with this authority when @@ -5120,8 +5154,8 @@ public class PackageManagerService extends IPackageManager.Stub { } String names[] = p.info.authority.split(";"); for (int j = 0; j < names.length; j++) { - if (mProviders.get(names[j]) == p) { - mProviders.remove(names[j]); + if (mProvidersByAuthority.get(names[j]) == p) { + mProvidersByAuthority.remove(names[j]); if (DEBUG_REMOVE) { if (chatty) Log.d(TAG, "Unregistered content provider: " + names[j] @@ -5962,6 +5996,195 @@ public class PackageManagerService extends IPackageManager.Stub { private int mFlags; }; + private final class ProviderIntentResolver + extends IntentResolver<PackageParser.ProviderIntentInfo, ResolveInfo> { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, + boolean defaultOnly, int userId) { + mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0; + return super.queryIntent(intent, resolvedType, defaultOnly, userId); + } + + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags, + int userId) { + if (!sUserManager.exists(userId)) + return null; + mFlags = flags; + return super.queryIntent(intent, resolvedType, + (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId); + } + + public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, + int flags, ArrayList<PackageParser.Provider> packageProviders, int userId) { + if (!sUserManager.exists(userId)) + return null; + if (packageProviders == null) { + return null; + } + mFlags = flags; + final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0; + final int N = packageProviders.size(); + ArrayList<PackageParser.ProviderIntentInfo[]> listCut = + new ArrayList<PackageParser.ProviderIntentInfo[]>(N); + + ArrayList<PackageParser.ProviderIntentInfo> intentFilters; + for (int i = 0; i < N; ++i) { + intentFilters = packageProviders.get(i).intents; + if (intentFilters != null && intentFilters.size() > 0) { + PackageParser.ProviderIntentInfo[] array = + new PackageParser.ProviderIntentInfo[intentFilters.size()]; + intentFilters.toArray(array); + listCut.add(array); + } + } + return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId); + } + + public final void addProvider(PackageParser.Provider p) { + mProviders.put(p.getComponentName(), p); + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " " + + (p.info.nonLocalizedLabel != null + ? p.info.nonLocalizedLabel : p.info.name) + ":"); + Log.v(TAG, " Class=" + p.info.name); + } + final int NI = p.intents.size(); + int j; + for (j = 0; j < NI; j++) { + PackageParser.ProviderIntentInfo intent = p.intents.get(j); + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " IntentFilter:"); + intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); + } + if (!intent.debugCheck()) { + Log.w(TAG, "==> For Provider " + p.info.name); + } + addFilter(intent); + } + } + + public final void removeProvider(PackageParser.Provider p) { + mProviders.remove(p.getComponentName()); + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " " + (p.info.nonLocalizedLabel != null + ? p.info.nonLocalizedLabel : p.info.name) + ":"); + Log.v(TAG, " Class=" + p.info.name); + } + final int NI = p.intents.size(); + int j; + for (j = 0; j < NI; j++) { + PackageParser.ProviderIntentInfo intent = p.intents.get(j); + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " IntentFilter:"); + intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); + } + removeFilter(intent); + } + } + + @Override + protected boolean allowFilterResult( + PackageParser.ProviderIntentInfo filter, List<ResolveInfo> dest) { + ProviderInfo filterPi = filter.provider.info; + for (int i = dest.size() - 1; i >= 0; i--) { + ProviderInfo destPi = dest.get(i).providerInfo; + if (destPi.name == filterPi.name + && destPi.packageName == filterPi.packageName) { + return false; + } + } + return true; + } + + @Override + protected PackageParser.ProviderIntentInfo[] newArray(int size) { + return new PackageParser.ProviderIntentInfo[size]; + } + + @Override + protected boolean isFilterStopped(PackageParser.ProviderIntentInfo filter, int userId) { + if (!sUserManager.exists(userId)) + return true; + PackageParser.Package p = filter.provider.owner; + if (p != null) { + PackageSetting ps = (PackageSetting) p.mExtras; + if (ps != null) { + // System apps are never considered stopped for purposes of + // filtering, because there may be no way for the user to + // actually re-launch them. + return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0 + && ps.getStopped(userId); + } + } + return false; + } + + @Override + protected boolean isPackageForFilter(String packageName, + PackageParser.ProviderIntentInfo info) { + return packageName.equals(info.provider.owner.packageName); + } + + @Override + protected ResolveInfo newResult(PackageParser.ProviderIntentInfo filter, + int match, int userId) { + if (!sUserManager.exists(userId)) + return null; + final PackageParser.ProviderIntentInfo info = filter; + if (!mSettings.isEnabledLPr(info.provider.info, mFlags, userId)) { + return null; + } + final PackageParser.Provider provider = info.provider; + if (mSafeMode && (provider.info.applicationInfo.flags + & ApplicationInfo.FLAG_SYSTEM) == 0) { + return null; + } + PackageSetting ps = (PackageSetting) provider.owner.mExtras; + if (ps == null) { + return null; + } + ProviderInfo pi = PackageParser.generateProviderInfo(provider, mFlags, + ps.readUserState(userId), userId); + if (pi == null) { + return null; + } + final ResolveInfo res = new ResolveInfo(); + res.providerInfo = pi; + if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) { + res.filter = filter; + } + res.priority = info.getPriority(); + res.preferredOrder = provider.owner.mPreferredOrder; + res.match = match; + res.isDefault = info.hasDefault; + res.labelRes = info.labelRes; + res.nonLocalizedLabel = info.nonLocalizedLabel; + res.icon = info.icon; + res.system = isSystemApp(res.providerInfo.applicationInfo); + return res; + } + + @Override + protected void sortResults(List<ResolveInfo> results) { + Collections.sort(results, mResolvePrioritySorter); + } + + @Override + protected void dumpFilter(PrintWriter out, String prefix, + PackageParser.ProviderIntentInfo filter) { + out.print(prefix); + out.print( + Integer.toHexString(System.identityHashCode(filter.provider))); + out.print(' '); + filter.provider.printComponentShortName(out); + out.print(" filter "); + out.println(Integer.toHexString(System.identityHashCode(filter))); + } + + private final HashMap<ComponentName, PackageParser.Provider> mProviders + = new HashMap<ComponentName, PackageParser.Provider>(); + private int mFlags; + }; + private static final Comparator<ResolveInfo> mResolvePrioritySorter = new Comparator<ResolveInfo>() { public int compare(ResolveInfo r1, ResolveInfo r2) { @@ -10454,6 +10677,11 @@ public class PackageManagerService extends IPackageManager.Stub { dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { dumpState.setTitlePrinted(true); } + if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:" + : "Provider Resolver Table:", " ", packageName, + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.setTitlePrinted(true); + } } if (dumpState.isDumping(DumpState.DUMP_PREFERRED)) { @@ -10498,7 +10726,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (dumpState.isDumping(DumpState.DUMP_PROVIDERS)) { boolean printedSomething = false; - for (PackageParser.Provider p : mProvidersByComponent.values()) { + for (PackageParser.Provider p : mProviders.mProviders.values()) { if (packageName != null && !packageName.equals(p.info.packageName)) { continue; } @@ -10512,7 +10740,8 @@ public class PackageManagerService extends IPackageManager.Stub { pw.print(" "); pw.println(p.toString()); } printedSomething = false; - for (Map.Entry<String, PackageParser.Provider> entry : mProviders.entrySet()) { + for (Map.Entry<String, PackageParser.Provider> entry : + mProvidersByAuthority.entrySet()) { PackageParser.Provider p = entry.getValue(); if (packageName != null && !packageName.equals(p.info.packageName)) { continue; diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 5f944f6107f5..661bd41632bc 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -282,6 +282,18 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + /** @hide */ + @Override + public List<ResolveInfo> queryIntentContentProvidersAsUser( + Intent intent, int flags, int userId) { + throw new UnsupportedOperationException(); + } + + @Override + public List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) { + throw new UnsupportedOperationException(); + } + @Override public ProviderInfo resolveContentProvider(String name, int flags) { throw new UnsupportedOperationException(); |