From 1bb6cf18c0039cb9d2a60713ea34d3709af58c5c Mon Sep 17 00:00:00 2001 From: Kenny Root Date: Fri, 15 Apr 2011 17:50:10 -0700 Subject: [PATCH] Break apart queries to getInstalled* API DO NOT MERGE To avoid blowing past the Binder IPC limit, change the PackageManagerService to have a DB-like interaction where the client tells the service the last "row" that it read. The fact that we use a HashMap instead of a TreeMap makes this problematic. For now we're just making a new ArrayList for the keys and then sorting them for each call. This can make the API slower for callers of this, but it's probably greatly overshadowed by the cost of the data transfer itself. Bug: 4064282 Change-Id: Ia3a8cdaa94581ed9336f2e05694b8c8a5f757bce --- cmds/pm/src/com/android/commands/pm/Pm.java | 21 ++- .../app/ApplicationPackageManager.java | 28 ++- .../android/content/pm/IPackageManager.aidl | 17 +- .../android/content/pm/ParceledListSlice.aidl | 19 ++ .../android/content/pm/ParceledListSlice.java | 170 ++++++++++++++++++ .../android/server/PackageManagerService.java | 146 ++++++++++----- 6 files changed, 347 insertions(+), 54 deletions(-) create mode 100755 core/java/android/content/pm/ParceledListSlice.aidl create mode 100644 core/java/android/content/pm/ParceledListSlice.java diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index b759de0d3e9fd..1b2326a43562a 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -30,14 +30,15 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.res.AssetManager; import android.content.res.Resources; import android.net.Uri; +import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; -import android.provider.Settings; import java.io.File; import java.lang.reflect.Field; @@ -228,7 +229,7 @@ public final class Pm { String filter = nextArg(); try { - List packages = mPm.getInstalledPackages(getFlags); + final List packages = getInstalledPackages(mPm, getFlags); int count = packages.size(); for (int p = 0 ; p < count ; p++) { @@ -256,6 +257,22 @@ public final class Pm { } } + @SuppressWarnings("unchecked") + private List getInstalledPackages(IPackageManager pm, int flags) + throws RemoteException { + final List packageInfos = new ArrayList(); + PackageInfo lastItem = null; + ParceledListSlice slice; + + do { + final String lastKey = lastItem != null ? lastItem.packageName : null; + slice = pm.getInstalledPackages(flags, lastKey); + lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR); + } while (!slice.isLastSlice()); + + return packageInfos; + } + /** * Lists all of the features supported by the current device. * diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 50e56c7926f3c..5926929174b8d 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -35,6 +35,7 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; @@ -44,6 +45,7 @@ import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.util.Log; @@ -378,19 +380,41 @@ final class ApplicationPackageManager extends PackageManager { throw new NameNotFoundException("No shared userid for user:"+sharedUserName); } + @SuppressWarnings("unchecked") @Override public List getInstalledPackages(int flags) { try { - return mPM.getInstalledPackages(flags); + final List packageInfos = new ArrayList(); + PackageInfo lastItem = null; + ParceledListSlice slice; + + do { + final String lastKey = lastItem != null ? lastItem.packageName : null; + slice = mPM.getInstalledPackages(flags, lastKey); + lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR); + } while (!slice.isLastSlice()); + + return packageInfos; } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } } + @SuppressWarnings("unchecked") @Override public List getInstalledApplications(int flags) { try { - return mPM.getInstalledApplications(flags); + final List applicationInfos = new ArrayList(); + ApplicationInfo lastItem = null; + ParceledListSlice slice; + + do { + final String lastKey = lastItem != null ? lastItem.packageName : null; + slice = mPM.getInstalledApplications(flags, lastKey); + lastItem = slice.populateList(applicationInfos, ApplicationInfo.CREATOR); + } while (!slice.isLastSlice()); + + return applicationInfos; } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index fbf8f92252917..20b1b50732d2c 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -30,6 +30,7 @@ import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; +import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; @@ -109,9 +110,21 @@ interface IPackageManager { List queryIntentServices(in Intent intent, String resolvedType, int flags); - List getInstalledPackages(int flags); + /** + * This implements getInstalledPackages via a "last returned row" + * mechanism that is not exposed in the API. This is to get around the IPC + * limit that kicks in when flags are included that bloat up the data + * returned. + */ + ParceledListSlice getInstalledPackages(int flags, in String lastRead); - List getInstalledApplications(int flags); + /** + * This implements getInstalledApplications via a "last returned row" + * mechanism that is not exposed in the API. This is to get around the IPC + * limit that kicks in when flags are included that bloat up the data + * returned. + */ + ParceledListSlice getInstalledApplications(int flags, in String lastRead); /** * Retrieve all applications that are marked as persistent. diff --git a/core/java/android/content/pm/ParceledListSlice.aidl b/core/java/android/content/pm/ParceledListSlice.aidl new file mode 100755 index 0000000000000..c02cc6a5472e5 --- /dev/null +++ b/core/java/android/content/pm/ParceledListSlice.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 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.content.pm; + +parcelable ParceledListSlice; diff --git a/core/java/android/content/pm/ParceledListSlice.java b/core/java/android/content/pm/ParceledListSlice.java new file mode 100644 index 0000000000000..f3a98db48b0bd --- /dev/null +++ b/core/java/android/content/pm/ParceledListSlice.java @@ -0,0 +1,170 @@ +/* + * 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.content.pm; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * Builds up a parcel that is discarded when written to another parcel or + * written to a list. This is useful for API that sends huge lists across a + * Binder that may be larger than the IPC limit. + * + * @hide + */ +public class ParceledListSlice implements Parcelable { + /* + * TODO get this number from somewhere else. For now set it to a quarter of + * the 1MB limit. + */ + private static final int MAX_IPC_SIZE = 256 * 1024; + + private Parcel mParcel; + + private int mNumItems; + + private boolean mIsLastSlice; + + public ParceledListSlice() { + mParcel = Parcel.obtain(); + } + + private ParceledListSlice(Parcel p, int numItems, boolean lastSlice) { + mParcel = p; + mNumItems = numItems; + mIsLastSlice = lastSlice; + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Write this to another Parcel. Note that this discards the internal Parcel + * and should not be used anymore. This is so we can pass this to a Binder + * where we won't have a chance to call recycle on this. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mNumItems); + dest.writeInt(mIsLastSlice ? 1 : 0); + + if (mNumItems > 0) { + final int parcelSize = mParcel.dataSize(); + dest.writeInt(parcelSize); + dest.appendFrom(mParcel, 0, parcelSize); + } + + mNumItems = 0; + mParcel.recycle(); + mParcel = null; + } + + /** + * Appends a parcel to this list slice. + * + * @param item Parcelable item to append to this list slice + * @return true when the list slice is full and should not be appended to + * anymore + */ + public boolean append(T item) { + if (mParcel == null) { + throw new IllegalStateException("ParceledListSlice has already been recycled"); + } + + item.writeToParcel(mParcel, PARCELABLE_WRITE_RETURN_VALUE); + mNumItems++; + + return mParcel.dataSize() > MAX_IPC_SIZE; + } + + /** + * Populates a list and discards the internal state of the + * ParceledListSlice in the process. The instance should + * not be used anymore. + * + * @param list list to insert items from this slice. + * @param creator creator that knows how to unparcel the + * target object type. + * @return the last item inserted into the list or null if none. + */ + public T populateList(List list, Creator creator) { + mParcel.setDataPosition(0); + + T item = null; + for (int i = 0; i < mNumItems; i++) { + item = creator.createFromParcel(mParcel); + list.add(item); + } + + mParcel.recycle(); + mParcel = null; + + return item; + } + + /** + * Sets whether this is the last list slice in the series. + * + * @param lastSlice + */ + public void setLastSlice(boolean lastSlice) { + mIsLastSlice = lastSlice; + } + + /** + * Returns whether this is the last slice in a series of slices. + * + * @return true if this is the last slice in the series. + */ + public boolean isLastSlice() { + return mIsLastSlice; + } + + @SuppressWarnings("unchecked") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public ParceledListSlice createFromParcel(Parcel in) { + final int numItems = in.readInt(); + final boolean lastSlice = in.readInt() == 1; + + if (numItems > 0) { + final int parcelSize = in.readInt(); + + // Advance within this Parcel + int offset = in.dataPosition(); + in.setDataPosition(offset + parcelSize); + + Parcel p = Parcel.obtain(); + p.setDataPosition(0); + p.appendFrom(in, offset, parcelSize); + p.setDataPosition(0); + + return new ParceledListSlice(p, numItems, lastSlice); + } else { + return new ParceledListSlice(); + } + } + + public ParceledListSlice[] newArray(int size) { + return new ParceledListSlice[size]; + } + }; +} diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 4a66a40c374e6..dd5c2a952fc74 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -55,6 +55,8 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; import android.content.pm.PackageStats; +import android.content.pm.ParceledListSlice; + import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; @@ -75,6 +77,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.os.Environment; import android.os.FileObserver; @@ -2331,63 +2334,110 @@ class PackageManagerService extends IPackageManager.Stub { } } - public List getInstalledPackages(int flags) { - ArrayList finalList = new ArrayList(); - - synchronized (mPackages) { - if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - Iterator i = mSettings.mPackages.values().iterator(); - while (i.hasNext()) { - final PackageSetting ps = i.next(); - PackageInfo psPkg = generatePackageInfoFromSettingsLP(ps.name, flags); - if(psPkg != null) { - finalList.add(psPkg); - } - } - } - else { - Iterator i = mPackages.values().iterator(); - while (i.hasNext()) { - final PackageParser.Package p = i.next(); - if (p.applicationInfo != null) { - PackageInfo pi = generatePackageInfo(p, flags); - if(pi != null) { - finalList.add(pi); - } - } - } + private static final int getContinuationPoint(final String[] keys, final String key) { + final int index; + if (key == null) { + index = 0; + } else { + final int insertPoint = Arrays.binarySearch(keys, key); + if (insertPoint < 0) { + index = -insertPoint; + } else { + index = insertPoint + 1; } } - return finalList; + return index; } - public List getInstalledApplications(int flags) { - ArrayList finalList = new ArrayList(); - synchronized(mPackages) { - if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - Iterator i = mSettings.mPackages.values().iterator(); - while (i.hasNext()) { - final PackageSetting ps = i.next(); - ApplicationInfo ai = generateApplicationInfoFromSettingsLP(ps.name, flags); - if(ai != null) { - finalList.add(ai); + public ParceledListSlice getInstalledPackages(int flags, String lastRead) { + final ParceledListSlice list = new ParceledListSlice(); + final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; + final String[] keys; + + synchronized (mPackages) { + if (listUninstalled) { + keys = mSettings.mPackages.keySet().toArray(new String[mSettings.mPackages.size()]); + } else { + keys = mPackages.keySet().toArray(new String[mPackages.size()]); + } + + Arrays.sort(keys); + int i = getContinuationPoint(keys, lastRead); + final int N = keys.length; + + while (i < N) { + final String packageName = keys[i++]; + + PackageInfo pi = null; + if (listUninstalled) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps != null) { + pi = generatePackageInfoFromSettingsLP(ps.name, flags); } + } else { + final PackageParser.Package p = mPackages.get(packageName); + if (p != null) { + pi = generatePackageInfo(p, flags); + } + } + + if (pi != null && !list.append(pi)) { + break; } } - else { - Iterator i = mPackages.values().iterator(); - while (i.hasNext()) { - final PackageParser.Package p = i.next(); - if (p.applicationInfo != null) { - ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags); - if(ai != null) { - finalList.add(ai); - } - } - } + + if (i == N) { + list.setLastSlice(true); } } - return finalList; + + return list; + } + + public ParceledListSlice getInstalledApplications(int flags, + String lastRead) { + final ParceledListSlice list = new ParceledListSlice(); + final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; + final String[] keys; + + synchronized (mPackages) { + if (listUninstalled) { + keys = mSettings.mPackages.keySet().toArray(new String[mSettings.mPackages.size()]); + } else { + keys = mPackages.keySet().toArray(new String[mPackages.size()]); + } + + Arrays.sort(keys); + int i = getContinuationPoint(keys, lastRead); + final int N = keys.length; + + while (i < N) { + final String packageName = keys[i++]; + + ApplicationInfo ai = null; + if (listUninstalled) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps != null) { + ai = generateApplicationInfoFromSettingsLP(ps.name, flags); + } + } else { + final PackageParser.Package p = mPackages.get(packageName); + if (p != null) { + ai = PackageParser.generateApplicationInfo(p, flags); + } + } + + if (ai != null && !list.append(ai)) { + break; + } + } + + if (i == N) { + list.setLastSlice(true); + } + } + + return list; } public List getPersistentApplications(int flags) {